From 78c996a7a1c6671e82541b64dc9b6f78224a708d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 23 Sep 2022 10:44:32 +0200 Subject: [PATCH 01/47] multi: add Session ID to struct This commit adds an ID to the Session struct. The ID is not persisted but is instead derived from the macaroon used for the session. --- litrpc/lit-sessions.pb.go | 116 ++++++++++++++++++++------------------ litrpc/lit-sessions.proto | 7 +-- session/interface.go | 2 + session/macaroon.go | 55 ++++++++++++++++++ session/tlv.go | 1 + session_rpcserver.go | 1 + 6 files changed, 123 insertions(+), 59 deletions(-) diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index 6f1220818..907070f6e 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -339,6 +339,7 @@ type Session struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + Id []byte `protobuf:"bytes,14,opt,name=id,proto3" json:"id,omitempty"` Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` SessionState SessionState `protobuf:"varint,2,opt,name=session_state,json=sessionState,proto3,enum=litrpc.SessionState" json:"session_state,omitempty"` SessionType SessionType `protobuf:"varint,3,opt,name=session_type,json=sessionType,proto3,enum=litrpc.SessionType" json:"session_type,omitempty"` @@ -394,6 +395,13 @@ func (*Session) Descriptor() ([]byte, []int) { return file_lit_sessions_proto_rawDescGZIP(), []int{3} } +func (x *Session) GetId() []byte { + if x != nil { + return x.Id + } + return nil +} + func (x *Session) GetLabel() string { if x != nil { return x.Label @@ -753,7 +761,8 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, - 0x86, 0x05, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x90, 0x05, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, @@ -792,60 +801,59 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0a, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, - 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x0e, - 0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, - 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, - 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, - 0x74, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, + 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x0f, + 0x10, 0x10, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, + 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x22, 0x15, 0x0a, 0x13, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2a, 0x93, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, + 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, + 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, + 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, + 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x5f, 0x50, 0x41, + 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, + 0x54, 0x10, 0x05, 0x22, 0x04, 0x08, 0x04, 0x10, 0x04, 0x2a, 0x59, 0x0a, 0x0c, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, + 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, + 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, 0x11, + 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, + 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, - 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x93, 0x01, 0x0a, 0x0b, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, - 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, - 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, - 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, - 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, - 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x49, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x19, - 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, - 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x05, 0x22, 0x04, 0x08, 0x04, 0x10, 0x04, 0x2a, - 0x59, 0x0a, 0x0c, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, - 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, - 0x53, 0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, - 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, - 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, - 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, - 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, - 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, + 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/lit-sessions.proto b/litrpc/lit-sessions.proto index b47897a08..21d988783 100644 --- a/litrpc/lit-sessions.proto +++ b/litrpc/lit-sessions.proto @@ -67,6 +67,8 @@ message AddSessionResponse { } message Session { + bytes id = 14; + string label = 1; SessionState session_state = 2; @@ -93,11 +95,6 @@ message Session { string account_id = 13; - /* - Field number 14 is reserved for future use. - */ - reserved 14; - /* Field number 15 is reserved for future use. */ diff --git a/session/interface.go b/session/interface.go index 30f55a612..4194fbb64 100644 --- a/session/interface.go +++ b/session/interface.go @@ -42,6 +42,7 @@ type MacaroonRecipe struct { // Session is a struct representing a long-term Terminal Connect session. type Session struct { + ID ID Label string State State Type Type @@ -83,6 +84,7 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, macRootKey := NewSuperMacaroonRootKeyID(macRootKeyBase) sess := &Session{ + ID: macRootKeyBase, Label: label, State: StateCreated, Type: typ, diff --git a/session/macaroon.go b/session/macaroon.go index 55b56d1e4..99d5e7dfa 100644 --- a/session/macaroon.go +++ b/session/macaroon.go @@ -5,6 +5,7 @@ import ( "context" "encoding/binary" "encoding/hex" + "fmt" "strconv" "github.com/lightningnetwork/lnd/lnrpc" @@ -19,6 +20,9 @@ var ( SuperMacaroonRootKeyPrefix = [4]byte{0xFF, 0xEE, 0xDD, 0xCC} ) +// ID represents the id of a session. +type ID [4]byte + // SuperMacaroonValidator is a function type for validating a super macaroon. type SuperMacaroonValidator func(ctx context.Context, superMacaroon []byte, requiredPermissions []bakery.Op, @@ -84,3 +88,54 @@ func isSuperMacaroonRootKeyID(rootKeyID uint64) bool { binary.BigEndian.PutUint64(rootKeyBytes, rootKeyID) return bytes.HasPrefix(rootKeyBytes, SuperMacaroonRootKeyPrefix[:]) } + +// IDFromMacaroon is a helper function that creates a session ID from +// a macaroon ID. +func IDFromMacaroon(mac *macaroon.Macaroon) (ID, error) { + rootKeyID, err := RootKeyIDFromMacaroon(mac) + if err != nil { + return ID{}, err + } + + return IDFromMacRootKeyID(rootKeyID), nil +} + +// IDFromMacRootKeyID converts a macaroon root key ID to a session ID. +func IDFromMacRootKeyID(rootKeyID uint64) ID { + rootKeyBytes := make([]byte, 8) + binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID) + + var id ID + copy(id[:], rootKeyBytes[4:]) + + return id +} + +// IDFromBytes is a helper function that creates a session ID from a byte slice. +func IDFromBytes(b []byte) (ID, error) { + var id ID + if len(b) != 4 { + return id, fmt.Errorf("session ID must be 4 bytes long") + } + copy(id[:], b) + return id, nil +} + +// RootKeyIDFromMacaroon extracts the root key ID of the passed macaroon. +func RootKeyIDFromMacaroon(mac *macaroon.Macaroon) (uint64, error) { + rawID := mac.Id() + if rawID[0] != byte(bakery.LatestVersion) { + return 0, fmt.Errorf("mac id is not on the latest version") + } + + decodedID := &lnrpc.MacaroonId{} + idProto := rawID[1:] + err := proto.Unmarshal(idProto, decodedID) + if err != nil { + return 0, err + } + + // The storage ID is a string representation of a 64-bit unsigned + // number. + return strconv.ParseUint(string(decodedID.StorageId), 10, 64) +} diff --git a/session/tlv.go b/session/tlv.go index 07d916f2e..9735a2685 100644 --- a/session/tlv.go +++ b/session/tlv.go @@ -164,6 +164,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { return nil, err } + session.ID = IDFromMacRootKeyID(session.MacaroonRootKey) session.Label = string(label) session.State = State(state) session.Type = Type(typ) diff --git a/session_rpcserver.go b/session_rpcserver.go index c8439479f..bd70250da 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -525,6 +525,7 @@ func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) { } return &litrpc.Session{ + Id: sess.ID[:], Label: sess.Label, SessionState: rpcState, SessionType: rpcType, From 8f0bc2f6cb70370a1e4adac76a673b49d55c53f7 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 20 May 2022 15:08:31 +0200 Subject: [PATCH 02/47] log+firewall: add firewall package --- firewall/log.go | 25 +++++++++++++++++++++++++ log.go | 4 ++++ 2 files changed, 29 insertions(+) create mode 100644 firewall/log.go diff --git a/firewall/log.go b/firewall/log.go new file mode 100644 index 000000000..20bb3bed6 --- /dev/null +++ b/firewall/log.go @@ -0,0 +1,25 @@ +package firewall + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "FIRE" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/log.go b/log.go index 424b60601..db679a4c9 100644 --- a/log.go +++ b/log.go @@ -5,6 +5,7 @@ import ( "github.com/lightninglabs/faraday" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/firewall" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightninglabs/loop/loopd" @@ -65,6 +66,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { lnd.AddSubLogger( root, accounts.Subsystem, intercept, accounts.UseLogger, ) + lnd.AddSubLogger( + root, firewall.Subsystem, intercept, firewall.UseLogger, + ) // Add daemon loggers to lnd's root logger. faraday.SetupLoggers(root, intercept) From 6d76e22d2d41bce73a7265d35e24fa977d428cfb Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 28 Apr 2022 11:39:02 +0200 Subject: [PATCH 03/47] firewall: add macaroon caveat logic --- firewall/caveats.go | 144 +++++++++++++++++++++++++++++++ firewall/caveats_test.go | 177 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 firewall/caveats.go create mode 100644 firewall/caveats_test.go diff --git a/firewall/caveats.go b/firewall/caveats.go new file mode 100644 index 000000000..d9f9476ed --- /dev/null +++ b/firewall/caveats.go @@ -0,0 +1,144 @@ +package firewall + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/lightningnetwork/lnd/macaroons" +) + +const ( + // RuleEnforcerCaveat is the name of the custom caveat that contains all + // the rules that need to be enforced in the firewall. + RuleEnforcerCaveat = "lit-mac-fw" + + // MetaInfoValuePrefix is the static prefix a macaroon caveat value has + // to mark the beginning of the meta information JSON data. + MetaInfoValuePrefix = "meta" + + // MetaRulesValuePrefix is the static prefix a macaroon caveat value has + // to mark the beginning of the rules list JSON data. + MetaRulesValuePrefix = "rules" +) + +var ( + // MetaInfoFullCaveatPrefix is the full prefix a caveat needs to have to + // be recognized as a meta information caveat. + MetaInfoFullCaveatPrefix = fmt.Sprintf("%s %s %s", + macaroons.CondLndCustom, RuleEnforcerCaveat, + MetaInfoValuePrefix) + + // MetaRulesFullCaveatPrefix is the full prefix a caveat needs to have + // to be recognized as a rules list caveat. + MetaRulesFullCaveatPrefix = fmt.Sprintf("%s %s %s", + macaroons.CondLndCustom, RuleEnforcerCaveat, + MetaRulesValuePrefix) + + // ErrNoMetaInfoCaveat is the error that is returned if a caveat doesn't + // have the prefix to be recognized as a meta information caveat. + ErrNoMetaInfoCaveat = fmt.Errorf("not a meta info caveat") + + // ErrNoRulesCaveat is the error that is returned if a caveat doesn't + // have the prefix to be recognized as a rules list caveat. + ErrNoRulesCaveat = fmt.Errorf("not a rules list caveat") +) + +// InterceptMetaInfo is the JSON serializable struct containing meta information +// about a request made by an automated node management software against LiT. +// The meta information is added as a macaroon caveat. +type InterceptMetaInfo struct { + // ActorName is the name of the actor service (=management software) + // that is issuing this request. + ActorName string `json:"actor_name"` + + // Trigger is the action or condition that triggered this intercepted + // request to be made. + Trigger string `json:"trigger"` + + // Intent is the desired outcome or end condition this request aims to + // arrive at. + Intent string `json:"intent"` +} + +// ToCaveat returns the full custom caveat string representation of the +// interception meta information in this format: +// lnd-custom lit-mac-fw meta: +func (i *InterceptMetaInfo) ToCaveat() (string, error) { + jsonBytes, err := json.Marshal(i) + if err != nil { + return "", fmt.Errorf("error JSON marshaling: %v", err) + } + + return fmt.Sprintf("%s:%s", MetaInfoFullCaveatPrefix, jsonBytes), nil +} + +// ParseMetaInfoCaveat tries to parse the given caveat string as a meta +// information struct. +func ParseMetaInfoCaveat(caveat string) (*InterceptMetaInfo, error) { + if !strings.HasPrefix(caveat, MetaInfoFullCaveatPrefix) { + return nil, ErrNoMetaInfoCaveat + } + + // Only the prefix isn't enough. + if len(caveat) <= len(MetaInfoFullCaveatPrefix)+1 { + return nil, ErrNoMetaInfoCaveat + } + + // There's a colon after the prefix that we need to skip as well. + jsonData := caveat[len(MetaInfoFullCaveatPrefix)+1:] + i := &InterceptMetaInfo{} + + if err := json.Unmarshal([]byte(jsonData), i); err != nil { + return nil, fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return i, nil +} + +// InterceptRule is the JSON serializable struct containing all the rules and +// their limits/settings that need to be enforced on a request made by an +// automated node management software against LiT. The rule information is added +// as a custom macaroon caveat. +type InterceptRule struct { + // Name is the name of the rule. It must correspond to a + Name string `json:"name"` + + // Restrictions is a key/value map of all the parameters that apply to + // this rule. + Restrictions map[string]string `json:"restrictions"` +} + +// RulesToCaveat encodes a list of rules as a full custom caveat string +// representation in this format: +// lnd-custom lit-mac-fw rules:[] +func RulesToCaveat(rules []*InterceptRule) (string, error) { + jsonBytes, err := json.Marshal(rules) + if err != nil { + return "", fmt.Errorf("error JSON marshaling: %v", err) + } + + return fmt.Sprintf("%s:%s", MetaRulesFullCaveatPrefix, jsonBytes), nil +} + +// ParseRuleCaveat tries to parse the given caveat string as a rule struct. +func ParseRuleCaveat(caveat string) ([]*InterceptRule, error) { + if !strings.HasPrefix(caveat, MetaRulesFullCaveatPrefix) { + return nil, ErrNoRulesCaveat + } + + // Only the prefix isn't enough. + if len(caveat) <= len(MetaRulesFullCaveatPrefix)+1 { + return nil, ErrNoRulesCaveat + } + + // There's a colon after the prefix that we need to skip as well. + jsonData := caveat[len(MetaRulesFullCaveatPrefix)+1:] + var rules []*InterceptRule + + if err := json.Unmarshal([]byte(jsonData), &rules); err != nil { + return nil, fmt.Errorf("error unmarshaling JSON: %v", err) + } + + return rules, nil +} diff --git a/firewall/caveats_test.go b/firewall/caveats_test.go new file mode 100644 index 000000000..11889a883 --- /dev/null +++ b/firewall/caveats_test.go @@ -0,0 +1,177 @@ +package firewall + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + testMetaCaveat = "lnd-custom lit-mac-fw meta:{\"actor_name\":" + + "\"re-balancer\",\"trigger\":\"channel 7413345453234435345 " + + "depleted\",\"intent\":\"increase outbound liquidity by " + + "2000000 sats\"}" + + testRulesCaveat = "lnd-custom lit-mac-fw rules:[{\"name\":" + + "\"re-balance-limits\",\"restrictions\":" + + "{\"first-hop-ignore-list\":\"03abcd...,02badb01...\"," + + "\"max-hops\":\"4\",\"off-chain-fees-sats\":\"10\"}}," + + "{\"name\":\"time-limits\",\"restrictions\":" + + "{\"re-balance-min-interval-seconds\":\"3600\"}}]" +) + +// TestInterceptMetaInfo makes sure that a meta information struct can be +// formatted as a caveat and then parsed again successfully. +func TestInterceptMetaInfo(t *testing.T) { + info := &InterceptMetaInfo{ + ActorName: "re-balancer", + Trigger: "channel 7413345453234435345 depleted", + Intent: "increase outbound liquidity by 2000000 sats", + } + + caveat, err := info.ToCaveat() + require.NoError(t, err) + + require.Equal(t, testMetaCaveat, caveat) + + parsedInfo, err := ParseMetaInfoCaveat(caveat) + require.NoError(t, err) + + require.Equal(t, info, parsedInfo) +} + +// TestParseMetaInfoCaveat makes sure the meta information caveat parsing works +// as expected. +func TestParseMetaInfoCaveat(t *testing.T) { + testCases := []struct { + name string + input string + err error + result *InterceptMetaInfo + }{{ + name: "empty string", + input: "", + err: ErrNoMetaInfoCaveat, + }, { + name: "prefix only", + input: "lnd-custom lit-mac-fw meta:", + err: ErrNoMetaInfoCaveat, + }, { + name: "invalid JSON", + input: "lnd-custom lit-mac-fw meta:bar", + err: fmt.Errorf("error unmarshaling JSON: invalid character " + + "'b' looking for beginning of value"), + }, { + name: "empty JSON", + input: "lnd-custom lit-mac-fw meta:{}", + result: &InterceptMetaInfo{}, + }} + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(tt *testing.T) { + i, err := ParseMetaInfoCaveat(tc.input) + + if tc.err != nil { + require.Error(tt, err) + require.Equal(tt, tc.err, err) + + return + } + + require.NoError(tt, err) + require.Equal(tt, tc.result, i) + }) + } +} + +// TestInterceptRule makes sure that a rules list struct can be formatted as a +// caveat and then parsed again successfully. +func TestInterceptRule(t *testing.T) { + rules := []*InterceptRule{{ + Name: "re-balance-limits", + Restrictions: map[string]string{ + "off-chain-fees-sats": "10", + "max-hops": "4", + "first-hop-ignore-list": "03abcd...,02badb01...", + }, + }, { + Name: "time-limits", + Restrictions: map[string]string{ + "re-balance-min-interval-seconds": "3600", + }, + }} + + caveat, err := RulesToCaveat(rules) + require.NoError(t, err) + + require.Equal(t, testRulesCaveat, caveat) + + parsedRules, err := ParseRuleCaveat(caveat) + require.NoError(t, err) + + require.Equal(t, rules, parsedRules) +} + +// TestParseRulesCaveat makes sure the rule list caveat parsing works as +// expected. +func TestParseRulesCaveat(t *testing.T) { + testCases := []struct { + name string + input string + err error + result []*InterceptRule + }{{ + name: "empty string", + input: "", + err: ErrNoRulesCaveat, + }, { + name: "prefix only", + input: "lnd-custom lit-mac-fw rules:", + err: ErrNoRulesCaveat, + }, { + name: "invalid JSON", + input: "lnd-custom lit-mac-fw rules:bar", + err: fmt.Errorf("error unmarshaling JSON: invalid character " + + "'b' looking for beginning of value"), + }, { + name: "empty JSON", + input: "lnd-custom lit-mac-fw rules:[]", + result: []*InterceptRule{}, + }, { + name: "empty rules", + input: "lnd-custom lit-mac-fw rules:[{}, {}]", + result: []*InterceptRule{{}, {}}, + }, { + name: "valid rules", + input: "lnd-custom lit-mac-fw rules:[{\"name\":\"foo\"}, " + + "{\"restrictions\":{\"foo\":\"bar\"}}]", + result: []*InterceptRule{{ + Name: "foo", + }, { + Restrictions: map[string]string{ + "foo": "bar", + }, + }}, + }} + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(tt *testing.T) { + i, err := ParseRuleCaveat(tc.input) + + if tc.err != nil { + require.Error(tt, err) + require.Equal(tt, tc.err, err) + + return + } + + require.NoError(tt, err) + require.Equal(tt, tc.result, i) + }) + } +} From 3f8789fb3eda9fd337a532bd952fbee0d7fb9ac0 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 28 Apr 2022 11:58:34 +0200 Subject: [PATCH 04/47] firewall: implement firewall --- firewall/request_info.go | 114 +++++++++++++++++++++++++++++++++++++ firewall/request_logger.go | 68 ++++++++++++++++++++++ firewall/rule_enforcer.go | 63 ++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 firewall/request_info.go create mode 100644 firewall/request_logger.go create mode 100644 firewall/rule_enforcer.go diff --git a/firewall/request_info.go b/firewall/request_info.go new file mode 100644 index 000000000..1b31adaee --- /dev/null +++ b/firewall/request_info.go @@ -0,0 +1,114 @@ +package firewall + +import ( + "fmt" + "strings" + + "github.com/lightningnetwork/lnd/lnrpc" + "gopkg.in/macaroon.v2" +) + +const ( + // MWRequestTypeStreamAuth represents the type name for a stream + // authentication interception message. + MWRequestTypeStreamAuth = "stream_auth" + + // MWRequestTypeRequest represents the type name for a request + // interception message. + MWRequestTypeRequest = "request" + + // MWRequestTypeResponse represents the type name for a response + // interception message. + MWRequestTypeResponse = "response" +) + +// RequestInfo stores the parsed representation of an incoming RPC middleware +// request. +type RequestInfo struct { + MsgID uint64 + RequestID uint64 + MWRequestType string + URI string + GRPCMessageType string + Streaming bool + Macaroon *macaroon.Macaroon + Caveats []string + MetaInfo *InterceptMetaInfo + Rules []*InterceptRule +} + +// NewInfoFromRequest parses the given RPC middleware interception request and +// returns a RequestInfo struct. +func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { + var ri *RequestInfo + switch t := req.InterceptType.(type) { + case *lnrpc.RPCMiddlewareRequest_StreamAuth: + ri = &RequestInfo{ + MWRequestType: MWRequestTypeStreamAuth, + URI: t.StreamAuth.MethodFullUri, + Streaming: true, + } + + case *lnrpc.RPCMiddlewareRequest_Request: + ri = &RequestInfo{ + MWRequestType: MWRequestTypeRequest, + URI: t.Request.MethodFullUri, + GRPCMessageType: t.Request.TypeName, + Streaming: t.Request.StreamRpc, + } + + case *lnrpc.RPCMiddlewareRequest_Response: + ri = &RequestInfo{ + MWRequestType: MWRequestTypeResponse, + URI: t.Response.MethodFullUri, + GRPCMessageType: t.Response.TypeName, + Streaming: t.Response.StreamRpc, + } + + default: + return nil, fmt.Errorf("invalid request type: %T", t) + } + + ri.MsgID = req.MsgId + ri.RequestID = req.RequestId + + ri.Macaroon = &macaroon.Macaroon{} + if err := ri.Macaroon.UnmarshalBinary(req.RawMacaroon); err != nil { + return nil, fmt.Errorf("error parsing macaroon: %v", err) + } + + ri.Caveats = make([]string, len(ri.Macaroon.Caveats())) + for idx, cav := range ri.Macaroon.Caveats() { + ri.Caveats[idx] = string(cav.Id) + + // Apply any meta information sent as a custom caveat. Only the + // last one will be considered if there are multiple caveats. + metaInfo, err := ParseMetaInfoCaveat(ri.Caveats[idx]) + if err == nil { + ri.MetaInfo = metaInfo + + // The same caveat can't be a meta info and rule list. + continue + } + + // Also apply the rule list sent as a custom caveat. Only the + // last set of rules will be considered if there are multiple + // caveats. + rules, err := ParseRuleCaveat(ri.Caveats[idx]) + if err == nil { + ri.Rules = rules + } + } + + return ri, nil +} + +// String returns the string representation of the request info struct. +func (ri *RequestInfo) String() string { + return fmt.Sprintf("Request={msg_id=%d, request_id=%d, type=%v, "+ + "uri=%v, grpc_message_type=%v, streaming=%v, caveats=[%v], "+ + "meta_info=%v, rules=[%v]}", + ri.MsgID, ri.RequestID, ri.MWRequestType, ri.URI, + ri.GRPCMessageType, ri.Streaming, strings.Join(ri.Caveats, ","), + ri.MetaInfo, ri.Rules) +} diff --git a/firewall/request_logger.go b/firewall/request_logger.go new file mode 100644 index 000000000..f7fe9c84a --- /dev/null +++ b/firewall/request_logger.go @@ -0,0 +1,68 @@ +package firewall + +import ( + "context" + "fmt" + + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightningnetwork/lnd/lnrpc" +) + +const ( + // RequestLoggerName is the name of the RequestLogger interceptor. + RequestLoggerName = "lit-macaroon-firewall-logger" +) + +// A compile-time assertion that RuleEnforcer is a +// rpcmiddleware.RequestInterceptor. +var _ mid.RequestInterceptor = (*RequestLogger)(nil) + +// RequestLogger is a RequestInterceptor that just logs incoming RPC requests. +type RequestLogger struct { + // ObservedRequests is a list of all requests that were observed because + // they contained additional meta information about their intent. + // + // TODO(guggero): Replace by persistent storage to keep a history for + // the user. + ObservedRequests []*RequestInfo +} + +// Name returns the name of the interceptor. +func (r *RequestLogger) Name() string { + return RequestLoggerName +} + +// ReadOnly returns true if this interceptor should be registered in read-only +// mode. In read-only mode no custom caveat name can be specified. +func (r *RequestLogger) ReadOnly() bool { + return true +} + +// CustomCaveatName returns the name of the custom caveat that is expected to be +// handled by this interceptor. Cannot be specified in read-only mode. +func (r *RequestLogger) CustomCaveatName() string { + return "" +} + +// Intercept processes an RPC middleware interception request and returns the +// interception result which either accepts or rejects the intercepted message. +func (r *RequestLogger) Intercept(_ context.Context, + req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) { + + ri, err := NewInfoFromRequest(req) + if err != nil { + return nil, fmt.Errorf("error parsing incoming RPC middleware "+ + "interception request: %v", err) + } + + log.Infof("RequestLogger: Intercepting %v", ri) + + // Persist the observed request if it is tagged with specific meta + // information. + if ri.MetaInfo != nil { + r.ObservedRequests = append(r.ObservedRequests, ri) + } + + // Send empty response, accepting the request. + return mid.RPCOk(req) +} diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go new file mode 100644 index 000000000..cdc653e68 --- /dev/null +++ b/firewall/rule_enforcer.go @@ -0,0 +1,63 @@ +package firewall + +import ( + "context" + "fmt" + + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightningnetwork/lnd/lnrpc" +) + +const ( + // RuleEnforcerName is the name of the RuleEnforcer interceptor. + RuleEnforcerName = "lit-macaroon-firewall" +) + +// A compile-time assertion that RuleEnforcer is a +// rpcmiddleware.RequestInterceptor. +var _ mid.RequestInterceptor = (*RuleEnforcer)(nil) + +// RuleEnforcer is a RequestInterceptor that makes sure all firewall related +// custom caveats in a macaroon are properly enforced. +type RuleEnforcer struct { +} + +// Name returns the name of the interceptor. +func (r *RuleEnforcer) Name() string { + return RuleEnforcerName +} + +// ReadOnly returns true if this interceptor should be registered in read-only +// mode. In read-only mode no custom caveat name can be specified. +func (r *RuleEnforcer) ReadOnly() bool { + return false +} + +// CustomCaveatName returns the name of the custom caveat that is expected to be +// handled by this interceptor. Cannot be specified in read-only mode. +func (r *RuleEnforcer) CustomCaveatName() string { + return RuleEnforcerCaveat +} + +// Intercept processes an RPC middleware interception request and returns the +// interception result which either accepts or rejects the intercepted message. +func (r *RuleEnforcer) Intercept(_ context.Context, + req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) { + + ri, err := NewInfoFromRequest(req) + if err != nil { + return nil, fmt.Errorf("error parsing incoming RPC middleware "+ + "interception request: %v", err) + } + + log.Infof("Enforcing rule %v", ri) + + // Enforce actual rules. + if len(ri.Rules) > 0 { + // TODO(guggero): Implement rules and their enforcement. + log.Debugf("There are %d rules to enforce", len(ri.Rules)) + } + + // Send empty response, accepting the request. + return mid.RPCOk(req) +} From bc61be5e29f2eafade5e3023a03257d7b065ff1d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 23 Nov 2022 10:42:47 +0700 Subject: [PATCH 05/47] firewall: allow request with no macaroon --- firewall/request_info.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/firewall/request_info.go b/firewall/request_info.go index 1b31adaee..511ba2ef7 100644 --- a/firewall/request_info.go +++ b/firewall/request_info.go @@ -72,6 +72,12 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { ri.MsgID = req.MsgId ri.RequestID = req.RequestId + // If there is no macaroon in the request, then there is nothing left + // to parse. + if len(req.RawMacaroon) == 0 { + return ri, nil + } + ri.Macaroon = &macaroon.Macaroon{} if err := ri.Macaroon.UnmarshalBinary(req.RawMacaroon); err != nil { return nil, fmt.Errorf("error parsing macaroon: %v", err) From f1d65e51e5ab350109136666a44b84f3e1cc935d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 20 May 2022 15:16:09 +0200 Subject: [PATCH 06/47] terminal: add firewall interepters to rpc middleware manager --- terminal.go | 55 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/terminal.go b/terminal.go index 800ddb011..be15e7e5f 100644 --- a/terminal.go +++ b/terminal.go @@ -22,6 +22,7 @@ import ( "github.com/lightninglabs/faraday/frdrpc" "github.com/lightninglabs/faraday/frdrpcserver" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/firewall" "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/queue" @@ -638,31 +639,39 @@ func (g *LightningTerminal) startSubservers() error { } g.sessionRpcServerStarted = true - if !g.cfg.RPCMiddleware.Disabled { - log.Infof("Starting LiT account service") - err := g.accountService.Start( - g.lndClient.Client, g.lndClient.Router, - g.lndClient.ChainParams, - ) - if err != nil { - return fmt.Errorf("error starting account service: %v", - err) - } - g.accountServiceStarted = true - - // Start the middleware manager. - log.Infof("Starting LiT middleware manager") - g.middleware = mid.NewManager( - g.cfg.RPCMiddleware.InterceptTimeout, - g.lndClient.Client, g.errQueue.ChanIn(), - g.accountService, - ) + // The rest of the function only applies if the rpc middleware + // interceptor has been enabled. + if g.cfg.RPCMiddleware.Disabled { + log.Infof("Internal sub server startup complete") - if err = g.middleware.Start(); err != nil { - return err - } - g.middlewareStarted = true + return nil + } + + log.Infof("Starting LiT account service") + err = g.accountService.Start( + g.lndClient.Client, g.lndClient.Router, + g.lndClient.ChainParams, + ) + if err != nil { + return fmt.Errorf("error starting account service: %v", + err) + } + g.accountServiceStarted = true + + // Start the middleware manager. + log.Infof("Starting LiT middleware manager") + g.middleware = mid.NewManager( + g.cfg.RPCMiddleware.InterceptTimeout, + g.lndClient.Client, g.errQueue.ChanIn(), + g.accountService, + &firewall.RequestLogger{}, + &firewall.RuleEnforcer{}, + ) + + if err = g.middleware.Start(); err != nil { + return err } + g.middlewareStarted = true log.Infof("Internal sub server startup complete") From edf195ebd9a16201b8b8cbe07785f7638afb72c9 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 8 Jun 2022 11:25:02 +0200 Subject: [PATCH 07/47] firewall: changes to initial structure --- firewall/caveats.go | 32 +++++++++------ firewall/caveats_test.go | 84 +++++++++++++++++++++------------------ firewall/request_info.go | 8 +++- firewall/rule_enforcer.go | 5 ++- 4 files changed, 75 insertions(+), 54 deletions(-) diff --git a/firewall/caveats.go b/firewall/caveats.go index d9f9476ed..e223bc6cd 100644 --- a/firewall/caveats.go +++ b/firewall/caveats.go @@ -52,6 +52,9 @@ type InterceptMetaInfo struct { // that is issuing this request. ActorName string `json:"actor_name"` + // Feature is the feature that caused the actor to execute this action. + Feature string `json:"feature"` + // Trigger is the action or condition that triggered this intercepted // request to be made. Trigger string `json:"trigger"` @@ -59,6 +62,10 @@ type InterceptMetaInfo struct { // Intent is the desired outcome or end condition this request aims to // arrive at. Intent string `json:"intent"` + + // StructuredJsonData is extra, structured, info that the Autopilot can + // send to Lit. It is a json serialised string. + StructuredJsonData string `json:"structured_json_data"` } // ToCaveat returns the full custom caveat string representation of the @@ -96,23 +103,24 @@ func ParseMetaInfoCaveat(caveat string) (*InterceptMetaInfo, error) { return i, nil } -// InterceptRule is the JSON serializable struct containing all the rules and +// InterceptRules is the JSON serializable struct containing all the rules and // their limits/settings that need to be enforced on a request made by an // automated node management software against LiT. The rule information is added // as a custom macaroon caveat. -type InterceptRule struct { - // Name is the name of the rule. It must correspond to a - Name string `json:"name"` - - // Restrictions is a key/value map of all the parameters that apply to - // this rule. - Restrictions map[string]string `json:"restrictions"` +type InterceptRules struct { + // SessionRules are rules that apply session wide. The map is rule + // name to rule value. + SessionRules map[string]string `json:"session_rules"` + + // Feature rules are rules that apply to a specific feature. The map is + // feature name to a map of rule name to rule value. + FeatureRules map[string]map[string]string `json:"feature_rules"` } // RulesToCaveat encodes a list of rules as a full custom caveat string // representation in this format: // lnd-custom lit-mac-fw rules:[] -func RulesToCaveat(rules []*InterceptRule) (string, error) { +func RulesToCaveat(rules *InterceptRules) (string, error) { jsonBytes, err := json.Marshal(rules) if err != nil { return "", fmt.Errorf("error JSON marshaling: %v", err) @@ -122,7 +130,7 @@ func RulesToCaveat(rules []*InterceptRule) (string, error) { } // ParseRuleCaveat tries to parse the given caveat string as a rule struct. -func ParseRuleCaveat(caveat string) ([]*InterceptRule, error) { +func ParseRuleCaveat(caveat string) (*InterceptRules, error) { if !strings.HasPrefix(caveat, MetaRulesFullCaveatPrefix) { return nil, ErrNoRulesCaveat } @@ -134,11 +142,11 @@ func ParseRuleCaveat(caveat string) ([]*InterceptRule, error) { // There's a colon after the prefix that we need to skip as well. jsonData := caveat[len(MetaRulesFullCaveatPrefix)+1:] - var rules []*InterceptRule + var rules InterceptRules if err := json.Unmarshal([]byte(jsonData), &rules); err != nil { return nil, fmt.Errorf("error unmarshaling JSON: %v", err) } - return rules, nil + return &rules, nil } diff --git a/firewall/caveats_test.go b/firewall/caveats_test.go index 11889a883..9d8e4b81d 100644 --- a/firewall/caveats_test.go +++ b/firewall/caveats_test.go @@ -9,25 +9,27 @@ import ( const ( testMetaCaveat = "lnd-custom lit-mac-fw meta:{\"actor_name\":" + - "\"re-balancer\",\"trigger\":\"channel 7413345453234435345 " + - "depleted\",\"intent\":\"increase outbound liquidity by " + - "2000000 sats\"}" - - testRulesCaveat = "lnd-custom lit-mac-fw rules:[{\"name\":" + - "\"re-balance-limits\",\"restrictions\":" + - "{\"first-hop-ignore-list\":\"03abcd...,02badb01...\"," + - "\"max-hops\":\"4\",\"off-chain-fees-sats\":\"10\"}}," + - "{\"name\":\"time-limits\",\"restrictions\":" + - "{\"re-balance-min-interval-seconds\":\"3600\"}}]" + "\"autopilot\",\"feature\":\"re-balance\",\"trigger\":" + + "\"channel 7413345453234435345 depleted\",\"intent\":" + + "\"increase outbound liquidity by 2000000 sats\"," + + "\"structured_json_data\":\"{}\"}" + + testRulesCaveat = "lnd-custom lit-mac-fw rules:{\"session_rules\":{" + + "\"rate-limit\":\"1/10\"},\"feature_rules\":{\"AutoFees\":{" + + "\"first-hop-ignore-list\":\"03abcd...,02badb01...\"," + + "\"max-hops\":\"4\"},\"Rebalance\":{\"off-chain-fees-sats\":" + + "\"10\",\"re-balance-min-interval-seconds\":\"3600\"}}}" ) // TestInterceptMetaInfo makes sure that a meta information struct can be // formatted as a caveat and then parsed again successfully. func TestInterceptMetaInfo(t *testing.T) { info := &InterceptMetaInfo{ - ActorName: "re-balancer", - Trigger: "channel 7413345453234435345 depleted", - Intent: "increase outbound liquidity by 2000000 sats", + ActorName: "autopilot", + Feature: "re-balance", + Trigger: "channel 7413345453234435345 depleted", + Intent: "increase outbound liquidity by 2000000 sats", + StructuredJsonData: "{}", } caveat, err := info.ToCaveat() @@ -90,19 +92,21 @@ func TestParseMetaInfoCaveat(t *testing.T) { // TestInterceptRule makes sure that a rules list struct can be formatted as a // caveat and then parsed again successfully. func TestInterceptRule(t *testing.T) { - rules := []*InterceptRule{{ - Name: "re-balance-limits", - Restrictions: map[string]string{ - "off-chain-fees-sats": "10", - "max-hops": "4", - "first-hop-ignore-list": "03abcd...,02badb01...", + rules := &InterceptRules{ + FeatureRules: map[string]map[string]string{ + "AutoFees": { + "first-hop-ignore-list": "03abcd...,02badb01...", + "max-hops": "4", + }, + "Rebalance": { + "off-chain-fees-sats": "10", + "re-balance-min-interval-seconds": "3600", + }, }, - }, { - Name: "time-limits", - Restrictions: map[string]string{ - "re-balance-min-interval-seconds": "3600", + SessionRules: map[string]string{ + "rate-limit": "1/10", }, - }} + } caveat, err := RulesToCaveat(rules) require.NoError(t, err) @@ -122,7 +126,7 @@ func TestParseRulesCaveat(t *testing.T) { name string input string err error - result []*InterceptRule + result *InterceptRules }{{ name: "empty string", input: "", @@ -138,23 +142,25 @@ func TestParseRulesCaveat(t *testing.T) { "'b' looking for beginning of value"), }, { name: "empty JSON", - input: "lnd-custom lit-mac-fw rules:[]", - result: []*InterceptRule{}, - }, { - name: "empty rules", - input: "lnd-custom lit-mac-fw rules:[{}, {}]", - result: []*InterceptRule{{}, {}}, + input: "lnd-custom lit-mac-fw rules:{}", + result: &InterceptRules{}, }, { name: "valid rules", - input: "lnd-custom lit-mac-fw rules:[{\"name\":\"foo\"}, " + - "{\"restrictions\":{\"foo\":\"bar\"}}]", - result: []*InterceptRule{{ - Name: "foo", - }, { - Restrictions: map[string]string{ - "foo": "bar", + input: "lnd-custom lit-mac-fw rules:{\"session_rules\":" + + "{\"rate-limit\":\"2000\"}, \"feature_rules\":" + + "{\"Autofees\":{\"foo\":\"bar\", \"rate-limit\":" + + "\"1000\"}}}", + result: &InterceptRules{ + FeatureRules: map[string]map[string]string{ + "Autofees": { + "foo": "bar", + "rate-limit": "1000", + }, }, - }}, + SessionRules: map[string]string{ + "rate-limit": "2000", + }, + }, }} for _, tc := range testCases { diff --git a/firewall/request_info.go b/firewall/request_info.go index 511ba2ef7..5f2914b21 100644 --- a/firewall/request_info.go +++ b/firewall/request_info.go @@ -30,11 +30,13 @@ type RequestInfo struct { MWRequestType string URI string GRPCMessageType string + IsError bool + Serialized []byte Streaming bool Macaroon *macaroon.Macaroon Caveats []string MetaInfo *InterceptMetaInfo - Rules []*InterceptRule + Rules *InterceptRules } // NewInfoFromRequest parses the given RPC middleware interception request and @@ -54,6 +56,8 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { MWRequestType: MWRequestTypeRequest, URI: t.Request.MethodFullUri, GRPCMessageType: t.Request.TypeName, + IsError: t.Request.IsError, + Serialized: t.Request.Serialized, Streaming: t.Request.StreamRpc, } @@ -62,6 +66,8 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { MWRequestType: MWRequestTypeResponse, URI: t.Response.MethodFullUri, GRPCMessageType: t.Response.TypeName, + IsError: t.Response.IsError, + Serialized: t.Response.Serialized, Streaming: t.Response.StreamRpc, } diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index cdc653e68..3289e9162 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -53,9 +53,10 @@ func (r *RuleEnforcer) Intercept(_ context.Context, log.Infof("Enforcing rule %v", ri) // Enforce actual rules. - if len(ri.Rules) > 0 { + numRules := len(ri.Rules.SessionRules) + len(ri.Rules.FeatureRules) + if numRules > 0 { // TODO(guggero): Implement rules and their enforcement. - log.Debugf("There are %d rules to enforce", len(ri.Rules)) + log.Debugf("There are %d rules to enforce", numRules) } // Send empty response, accepting the request. From 69516ec675d8f2d32c9e2735b12cc86d3f3b9f21 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 30 Sep 2022 18:41:48 +0200 Subject: [PATCH 08/47] litrpc: add lit-autopilot protos In this commit we add all the proto methods and messages that we will need for the initial autopilot implementation. --- .github/workflows/main.yml | 3 + Makefile | 4 + app/src/types/generated/lit-sessions_pb.d.ts | 338 +++ app/src/types/generated/lit-sessions_pb.js | 2389 +++++++++++++++++- app/src/util/tests/sampleData.ts | 130 + litclient/jsoncallbacks.go | 4 + litrpc/Dockerfile | 4 +- litrpc/accounts.pb.json.go | 123 + litrpc/autopilot.pb.json.go | 123 + litrpc/gen_protos.sh | 19 +- litrpc/lit-autopilot.pb.go | 1111 ++++++++ litrpc/lit-autopilot.proto | 174 ++ litrpc/lit-autopilot_grpc.pb.go | 209 ++ litrpc/lit-sessions.pb.go | 1365 ++++++++-- litrpc/lit-sessions.proto | 129 +- litrpc/sessions.pb.json.go | 98 + 16 files changed, 6054 insertions(+), 169 deletions(-) create mode 100644 litrpc/accounts.pb.json.go create mode 100644 litrpc/autopilot.pb.json.go create mode 100644 litrpc/lit-autopilot.pb.go create mode 100644 litrpc/lit-autopilot.proto create mode 100644 litrpc/lit-autopilot_grpc.pb.go create mode 100644 litrpc/sessions.pb.json.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 342dea06f..41fc56d9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -114,6 +114,9 @@ jobs: working-directory: ./app run: yarn + - name: run check + run: make rpc-js-compile && make protos-check + - name: compile rpc for golang run: make rpc diff --git a/Makefile b/Makefile index c24f728b3..9bc20e968 100644 --- a/Makefile +++ b/Makefile @@ -228,6 +228,10 @@ protos-check: protos @$(call print, "Verifying compiled protos.") if test -n "$$(git describe --dirty | grep dirty)"; then echo "Protos not properly formatted or not compiled with correct version"; git status; git diff; exit 1; fi +rpc-js-compile: + @$(call print, "Compiling JSON/WASM stubs.") + GOOS=js GOARCH=wasm $(GOBUILD) $(PKG)/litrpc + clean: @$(call print, "Cleaning source.$(NC)") $(RM) ./litcli-debug diff --git a/app/src/types/generated/lit-sessions_pb.d.ts b/app/src/types/generated/lit-sessions_pb.d.ts index 4b6a00d76..5b2b7bf81 100644 --- a/app/src/types/generated/lit-sessions_pb.d.ts +++ b/app/src/types/generated/lit-sessions_pb.d.ts @@ -96,6 +96,11 @@ export namespace AddSessionResponse { } export class Session extends jspb.Message { + getId(): Uint8Array | string; + getId_asU8(): Uint8Array; + getId_asB64(): string; + setId(value: Uint8Array | string): void; + getLabel(): string; setLabel(value: string): void; @@ -143,6 +148,8 @@ export class Session extends jspb.Message { getAccountId(): string; setAccountId(value: string): void; + getAutopilotFeatureInfoMap(): jspb.Map; + clearAutopilotFeatureInfoMap(): void; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Session.AsObject; static toObject(includeInstance: boolean, msg: Session): Session.AsObject; @@ -155,6 +162,7 @@ export class Session extends jspb.Message { export namespace Session { export type AsObject = { + id: Uint8Array | string, label: string, sessionState: SessionStateMap[keyof SessionStateMap], sessionType: SessionTypeMap[keyof SessionTypeMap], @@ -168,6 +176,7 @@ export namespace Session { createdAt: string, macaroonRecipe?: MacaroonRecipe.AsObject, accountId: string, + autopilotFeatureInfoMap: Array<[string, RulesMap.AsObject]>, } } @@ -275,11 +284,340 @@ export namespace RevokeSessionResponse { } } +export class RulesMap extends jspb.Message { + getRulesMap(): jspb.Map; + clearRulesMap(): void; + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RulesMap.AsObject; + static toObject(includeInstance: boolean, msg: RulesMap): RulesMap.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RulesMap, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RulesMap; + static deserializeBinaryFromReader(message: RulesMap, reader: jspb.BinaryReader): RulesMap; +} + +export namespace RulesMap { + export type AsObject = { + rulesMap: Array<[string, RuleValue.AsObject]>, + } +} + +export class RuleValue extends jspb.Message { + hasRateLimit(): boolean; + clearRateLimit(): void; + getRateLimit(): RateLimit | undefined; + setRateLimit(value?: RateLimit): void; + + hasChanPolicyBounds(): boolean; + clearChanPolicyBounds(): void; + getChanPolicyBounds(): ChannelPolicyBounds | undefined; + setChanPolicyBounds(value?: ChannelPolicyBounds): void; + + hasHistoryLimit(): boolean; + clearHistoryLimit(): void; + getHistoryLimit(): HistoryLimit | undefined; + setHistoryLimit(value?: HistoryLimit): void; + + hasOffChainBudget(): boolean; + clearOffChainBudget(): void; + getOffChainBudget(): OffChainBudget | undefined; + setOffChainBudget(value?: OffChainBudget): void; + + hasOnChainBudget(): boolean; + clearOnChainBudget(): void; + getOnChainBudget(): OnChainBudget | undefined; + setOnChainBudget(value?: OnChainBudget): void; + + hasSendToSelf(): boolean; + clearSendToSelf(): void; + getSendToSelf(): SendToSelf | undefined; + setSendToSelf(value?: SendToSelf): void; + + hasChannelRestrict(): boolean; + clearChannelRestrict(): void; + getChannelRestrict(): ChannelRestrict | undefined; + setChannelRestrict(value?: ChannelRestrict): void; + + hasPeerRestrict(): boolean; + clearPeerRestrict(): void; + getPeerRestrict(): PeerRestrict | undefined; + setPeerRestrict(value?: PeerRestrict): void; + + getValueCase(): RuleValue.ValueCase; + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RuleValue.AsObject; + static toObject(includeInstance: boolean, msg: RuleValue): RuleValue.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RuleValue, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RuleValue; + static deserializeBinaryFromReader(message: RuleValue, reader: jspb.BinaryReader): RuleValue; +} + +export namespace RuleValue { + export type AsObject = { + rateLimit?: RateLimit.AsObject, + chanPolicyBounds?: ChannelPolicyBounds.AsObject, + historyLimit?: HistoryLimit.AsObject, + offChainBudget?: OffChainBudget.AsObject, + onChainBudget?: OnChainBudget.AsObject, + sendToSelf?: SendToSelf.AsObject, + channelRestrict?: ChannelRestrict.AsObject, + peerRestrict?: PeerRestrict.AsObject, + } + + export enum ValueCase { + VALUE_NOT_SET = 0, + RATE_LIMIT = 1, + CHAN_POLICY_BOUNDS = 2, + HISTORY_LIMIT = 3, + OFF_CHAIN_BUDGET = 4, + ON_CHAIN_BUDGET = 5, + SEND_TO_SELF = 6, + CHANNEL_RESTRICT = 7, + PEER_RESTRICT = 8, + } +} + +export class RateLimit extends jspb.Message { + hasReadLimit(): boolean; + clearReadLimit(): void; + getReadLimit(): Rate | undefined; + setReadLimit(value?: Rate): void; + + hasWriteLimit(): boolean; + clearWriteLimit(): void; + getWriteLimit(): Rate | undefined; + setWriteLimit(value?: Rate): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RateLimit.AsObject; + static toObject(includeInstance: boolean, msg: RateLimit): RateLimit.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RateLimit, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RateLimit; + static deserializeBinaryFromReader(message: RateLimit, reader: jspb.BinaryReader): RateLimit; +} + +export namespace RateLimit { + export type AsObject = { + readLimit?: Rate.AsObject, + writeLimit?: Rate.AsObject, + } +} + +export class Rate extends jspb.Message { + getIterations(): number; + setIterations(value: number): void; + + getNumHours(): number; + setNumHours(value: number): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Rate.AsObject; + static toObject(includeInstance: boolean, msg: Rate): Rate.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Rate, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Rate; + static deserializeBinaryFromReader(message: Rate, reader: jspb.BinaryReader): Rate; +} + +export namespace Rate { + export type AsObject = { + iterations: number, + numHours: number, + } +} + +export class HistoryLimit extends jspb.Message { + getStartTime(): string; + setStartTime(value: string): void; + + getDuration(): string; + setDuration(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): HistoryLimit.AsObject; + static toObject(includeInstance: boolean, msg: HistoryLimit): HistoryLimit.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: HistoryLimit, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): HistoryLimit; + static deserializeBinaryFromReader(message: HistoryLimit, reader: jspb.BinaryReader): HistoryLimit; +} + +export namespace HistoryLimit { + export type AsObject = { + startTime: string, + duration: string, + } +} + +export class ChannelPolicyBounds extends jspb.Message { + getMinBaseMsat(): string; + setMinBaseMsat(value: string): void; + + getMaxBaseMsat(): string; + setMaxBaseMsat(value: string): void; + + getMinRatePpm(): number; + setMinRatePpm(value: number): void; + + getMaxRatePpm(): number; + setMaxRatePpm(value: number): void; + + getMinCltvDelta(): number; + setMinCltvDelta(value: number): void; + + getMaxCltvDelta(): number; + setMaxCltvDelta(value: number): void; + + getMinHtlcMsat(): string; + setMinHtlcMsat(value: string): void; + + getMaxHtlcMsat(): string; + setMaxHtlcMsat(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): ChannelPolicyBounds.AsObject; + static toObject(includeInstance: boolean, msg: ChannelPolicyBounds): ChannelPolicyBounds.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: ChannelPolicyBounds, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): ChannelPolicyBounds; + static deserializeBinaryFromReader(message: ChannelPolicyBounds, reader: jspb.BinaryReader): ChannelPolicyBounds; +} + +export namespace ChannelPolicyBounds { + export type AsObject = { + minBaseMsat: string, + maxBaseMsat: string, + minRatePpm: number, + maxRatePpm: number, + minCltvDelta: number, + maxCltvDelta: number, + minHtlcMsat: string, + maxHtlcMsat: string, + } +} + +export class OffChainBudget extends jspb.Message { + getMaxAmtMsat(): string; + setMaxAmtMsat(value: string): void; + + getMaxFeesMsat(): string; + setMaxFeesMsat(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): OffChainBudget.AsObject; + static toObject(includeInstance: boolean, msg: OffChainBudget): OffChainBudget.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: OffChainBudget, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): OffChainBudget; + static deserializeBinaryFromReader(message: OffChainBudget, reader: jspb.BinaryReader): OffChainBudget; +} + +export namespace OffChainBudget { + export type AsObject = { + maxAmtMsat: string, + maxFeesMsat: string, + } +} + +export class OnChainBudget extends jspb.Message { + getAbsoluteAmtSats(): string; + setAbsoluteAmtSats(value: string): void; + + getMaxSatPerVByte(): string; + setMaxSatPerVByte(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): OnChainBudget.AsObject; + static toObject(includeInstance: boolean, msg: OnChainBudget): OnChainBudget.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: OnChainBudget, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): OnChainBudget; + static deserializeBinaryFromReader(message: OnChainBudget, reader: jspb.BinaryReader): OnChainBudget; +} + +export namespace OnChainBudget { + export type AsObject = { + absoluteAmtSats: string, + maxSatPerVByte: string, + } +} + +export class SendToSelf extends jspb.Message { + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SendToSelf.AsObject; + static toObject(includeInstance: boolean, msg: SendToSelf): SendToSelf.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SendToSelf, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SendToSelf; + static deserializeBinaryFromReader(message: SendToSelf, reader: jspb.BinaryReader): SendToSelf; +} + +export namespace SendToSelf { + export type AsObject = { + } +} + +export class ChannelRestrict extends jspb.Message { + clearChannelIdsList(): void; + getChannelIdsList(): Array; + setChannelIdsList(value: Array): void; + addChannelIds(value: string, index?: number): string; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): ChannelRestrict.AsObject; + static toObject(includeInstance: boolean, msg: ChannelRestrict): ChannelRestrict.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: ChannelRestrict, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): ChannelRestrict; + static deserializeBinaryFromReader(message: ChannelRestrict, reader: jspb.BinaryReader): ChannelRestrict; +} + +export namespace ChannelRestrict { + export type AsObject = { + channelIdsList: Array, + } +} + +export class PeerRestrict extends jspb.Message { + clearPeerIdsList(): void; + getPeerIdsList(): Array; + setPeerIdsList(value: Array): void; + addPeerIds(value: string, index?: number): string; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): PeerRestrict.AsObject; + static toObject(includeInstance: boolean, msg: PeerRestrict): PeerRestrict.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: PeerRestrict, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): PeerRestrict; + static deserializeBinaryFromReader(message: PeerRestrict, reader: jspb.BinaryReader): PeerRestrict; +} + +export namespace PeerRestrict { + export type AsObject = { + peerIdsList: Array, + } +} + export interface SessionTypeMap { TYPE_MACAROON_READONLY: 0; TYPE_MACAROON_ADMIN: 1; TYPE_MACAROON_CUSTOM: 2; TYPE_UI_PASSWORD: 3; + TYPE_AUTOPILOT: 4; TYPE_MACAROON_ACCOUNT: 5; } diff --git a/app/src/types/generated/lit-sessions_pb.js b/app/src/types/generated/lit-sessions_pb.js index 82730227b..81a67cac4 100644 --- a/app/src/types/generated/lit-sessions_pb.js +++ b/app/src/types/generated/lit-sessions_pb.js @@ -16,12 +16,23 @@ var global = Function('return this')(); goog.exportSymbol('proto.litrpc.AddSessionRequest', null, global); goog.exportSymbol('proto.litrpc.AddSessionResponse', null, global); +goog.exportSymbol('proto.litrpc.ChannelPolicyBounds', null, global); +goog.exportSymbol('proto.litrpc.ChannelRestrict', null, global); +goog.exportSymbol('proto.litrpc.HistoryLimit', null, global); goog.exportSymbol('proto.litrpc.ListSessionsRequest', null, global); goog.exportSymbol('proto.litrpc.ListSessionsResponse', null, global); goog.exportSymbol('proto.litrpc.MacaroonPermission', null, global); goog.exportSymbol('proto.litrpc.MacaroonRecipe', null, global); +goog.exportSymbol('proto.litrpc.OffChainBudget', null, global); +goog.exportSymbol('proto.litrpc.OnChainBudget', null, global); +goog.exportSymbol('proto.litrpc.PeerRestrict', null, global); +goog.exportSymbol('proto.litrpc.Rate', null, global); +goog.exportSymbol('proto.litrpc.RateLimit', null, global); goog.exportSymbol('proto.litrpc.RevokeSessionRequest', null, global); goog.exportSymbol('proto.litrpc.RevokeSessionResponse', null, global); +goog.exportSymbol('proto.litrpc.RuleValue', null, global); +goog.exportSymbol('proto.litrpc.RulesMap', null, global); +goog.exportSymbol('proto.litrpc.SendToSelf', null, global); goog.exportSymbol('proto.litrpc.Session', null, global); goog.exportSymbol('proto.litrpc.SessionState', null, global); goog.exportSymbol('proto.litrpc.SessionType', null, global); @@ -732,6 +743,7 @@ proto.litrpc.Session.prototype.toObject = function(opt_includeInstance) { */ proto.litrpc.Session.toObject = function(includeInstance, msg) { var f, obj = { + id: msg.getId_asB64(), label: jspb.Message.getFieldWithDefault(msg, 1, ""), sessionState: jspb.Message.getFieldWithDefault(msg, 2, 0), sessionType: jspb.Message.getFieldWithDefault(msg, 3, 0), @@ -744,7 +756,8 @@ proto.litrpc.Session.toObject = function(includeInstance, msg) { remotePublicKey: msg.getRemotePublicKey_asB64(), createdAt: jspb.Message.getFieldWithDefault(msg, 11, "0"), macaroonRecipe: (f = msg.getMacaroonRecipe()) && proto.litrpc.MacaroonRecipe.toObject(includeInstance, f), - accountId: jspb.Message.getFieldWithDefault(msg, 13, "") + accountId: jspb.Message.getFieldWithDefault(msg, 13, ""), + autopilotFeatureInfoMap: (f = msg.getAutopilotFeatureInfoMap()) ? f.toObject(includeInstance, proto.litrpc.RulesMap.toObject) : [] }; if (includeInstance) { @@ -781,6 +794,10 @@ proto.litrpc.Session.deserializeBinaryFromReader = function(msg, reader) { } var field = reader.getFieldNumber(); switch (field) { + case 14: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setId(value); + break; case 1: var value = /** @type {string} */ (reader.readString()); msg.setLabel(value); @@ -834,6 +851,12 @@ proto.litrpc.Session.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {string} */ (reader.readString()); msg.setAccountId(value); break; + case 15: + var value = msg.getAutopilotFeatureInfoMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.litrpc.RulesMap.deserializeBinaryFromReader, ""); + }); + break; default: reader.skipField(); break; @@ -863,6 +886,13 @@ proto.litrpc.Session.prototype.serializeBinary = function() { */ proto.litrpc.Session.serializeBinaryToWriter = function(message, writer) { var f = undefined; + f = message.getId_asU8(); + if (f.length > 0) { + writer.writeBytes( + 14, + f + ); + } f = message.getLabel(); if (f.length > 0) { writer.writeString( @@ -955,6 +985,49 @@ proto.litrpc.Session.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getAutopilotFeatureInfoMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(15, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.litrpc.RulesMap.serializeBinaryToWriter); + } +}; + + +/** + * optional bytes id = 14; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.Session.prototype.getId = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 14, "")); +}; + + +/** + * optional bytes id = 14; + * This is a type-conversion wrapper around `getId()` + * @return {string} + */ +proto.litrpc.Session.prototype.getId_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getId())); +}; + + +/** + * optional bytes id = 14; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getId()` + * @return {!Uint8Array} + */ +proto.litrpc.Session.prototype.getId_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getId())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.litrpc.Session.prototype.setId = function(value) { + jspb.Message.setProto3BytesField(this, 14, value); }; @@ -1242,6 +1315,24 @@ proto.litrpc.Session.prototype.setAccountId = function(value) { }; +/** + * map autopilot_feature_info = 15; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.litrpc.Session.prototype.getAutopilotFeatureInfoMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 15, opt_noLazyCreate, + proto.litrpc.RulesMap)); +}; + + +proto.litrpc.Session.prototype.clearAutopilotFeatureInfoMap = function() { + this.getAutopilotFeatureInfoMap().clear(); +}; + + /** * Generated by JsPbCodeGenerator. @@ -2017,6 +2108,2301 @@ proto.litrpc.RevokeSessionResponse.serializeBinaryToWriter = function(message, w }; + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.RulesMap = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.RulesMap, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.RulesMap.displayName = 'proto.litrpc.RulesMap'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.RulesMap.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.RulesMap.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.RulesMap} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RulesMap.toObject = function(includeInstance, msg) { + var f, obj = { + rulesMap: (f = msg.getRulesMap()) ? f.toObject(includeInstance, proto.litrpc.RuleValue.toObject) : [] + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.RulesMap} + */ +proto.litrpc.RulesMap.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.RulesMap; + return proto.litrpc.RulesMap.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.RulesMap} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.RulesMap} + */ +proto.litrpc.RulesMap.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = msg.getRulesMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.litrpc.RuleValue.deserializeBinaryFromReader, ""); + }); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.RulesMap.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.RulesMap.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.RulesMap} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RulesMap.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRulesMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(1, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.litrpc.RuleValue.serializeBinaryToWriter); + } +}; + + +/** + * map rules = 1; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.litrpc.RulesMap.prototype.getRulesMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 1, opt_noLazyCreate, + proto.litrpc.RuleValue)); +}; + + +proto.litrpc.RulesMap.prototype.clearRulesMap = function() { + this.getRulesMap().clear(); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.RuleValue = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.litrpc.RuleValue.oneofGroups_); +}; +goog.inherits(proto.litrpc.RuleValue, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.RuleValue.displayName = 'proto.litrpc.RuleValue'; +} +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.litrpc.RuleValue.oneofGroups_ = [[1,2,3,4,5,6,7,8]]; + +/** + * @enum {number} + */ +proto.litrpc.RuleValue.ValueCase = { + VALUE_NOT_SET: 0, + RATE_LIMIT: 1, + CHAN_POLICY_BOUNDS: 2, + HISTORY_LIMIT: 3, + OFF_CHAIN_BUDGET: 4, + ON_CHAIN_BUDGET: 5, + SEND_TO_SELF: 6, + CHANNEL_RESTRICT: 7, + PEER_RESTRICT: 8 +}; + +/** + * @return {proto.litrpc.RuleValue.ValueCase} + */ +proto.litrpc.RuleValue.prototype.getValueCase = function() { + return /** @type {proto.litrpc.RuleValue.ValueCase} */(jspb.Message.computeOneofCase(this, proto.litrpc.RuleValue.oneofGroups_[0])); +}; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.RuleValue.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.RuleValue.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.RuleValue} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RuleValue.toObject = function(includeInstance, msg) { + var f, obj = { + rateLimit: (f = msg.getRateLimit()) && proto.litrpc.RateLimit.toObject(includeInstance, f), + chanPolicyBounds: (f = msg.getChanPolicyBounds()) && proto.litrpc.ChannelPolicyBounds.toObject(includeInstance, f), + historyLimit: (f = msg.getHistoryLimit()) && proto.litrpc.HistoryLimit.toObject(includeInstance, f), + offChainBudget: (f = msg.getOffChainBudget()) && proto.litrpc.OffChainBudget.toObject(includeInstance, f), + onChainBudget: (f = msg.getOnChainBudget()) && proto.litrpc.OnChainBudget.toObject(includeInstance, f), + sendToSelf: (f = msg.getSendToSelf()) && proto.litrpc.SendToSelf.toObject(includeInstance, f), + channelRestrict: (f = msg.getChannelRestrict()) && proto.litrpc.ChannelRestrict.toObject(includeInstance, f), + peerRestrict: (f = msg.getPeerRestrict()) && proto.litrpc.PeerRestrict.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.RuleValue} + */ +proto.litrpc.RuleValue.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.RuleValue; + return proto.litrpc.RuleValue.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.RuleValue} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.RuleValue} + */ +proto.litrpc.RuleValue.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.litrpc.RateLimit; + reader.readMessage(value,proto.litrpc.RateLimit.deserializeBinaryFromReader); + msg.setRateLimit(value); + break; + case 2: + var value = new proto.litrpc.ChannelPolicyBounds; + reader.readMessage(value,proto.litrpc.ChannelPolicyBounds.deserializeBinaryFromReader); + msg.setChanPolicyBounds(value); + break; + case 3: + var value = new proto.litrpc.HistoryLimit; + reader.readMessage(value,proto.litrpc.HistoryLimit.deserializeBinaryFromReader); + msg.setHistoryLimit(value); + break; + case 4: + var value = new proto.litrpc.OffChainBudget; + reader.readMessage(value,proto.litrpc.OffChainBudget.deserializeBinaryFromReader); + msg.setOffChainBudget(value); + break; + case 5: + var value = new proto.litrpc.OnChainBudget; + reader.readMessage(value,proto.litrpc.OnChainBudget.deserializeBinaryFromReader); + msg.setOnChainBudget(value); + break; + case 6: + var value = new proto.litrpc.SendToSelf; + reader.readMessage(value,proto.litrpc.SendToSelf.deserializeBinaryFromReader); + msg.setSendToSelf(value); + break; + case 7: + var value = new proto.litrpc.ChannelRestrict; + reader.readMessage(value,proto.litrpc.ChannelRestrict.deserializeBinaryFromReader); + msg.setChannelRestrict(value); + break; + case 8: + var value = new proto.litrpc.PeerRestrict; + reader.readMessage(value,proto.litrpc.PeerRestrict.deserializeBinaryFromReader); + msg.setPeerRestrict(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.RuleValue.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.RuleValue.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.RuleValue} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RuleValue.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRateLimit(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.litrpc.RateLimit.serializeBinaryToWriter + ); + } + f = message.getChanPolicyBounds(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.litrpc.ChannelPolicyBounds.serializeBinaryToWriter + ); + } + f = message.getHistoryLimit(); + if (f != null) { + writer.writeMessage( + 3, + f, + proto.litrpc.HistoryLimit.serializeBinaryToWriter + ); + } + f = message.getOffChainBudget(); + if (f != null) { + writer.writeMessage( + 4, + f, + proto.litrpc.OffChainBudget.serializeBinaryToWriter + ); + } + f = message.getOnChainBudget(); + if (f != null) { + writer.writeMessage( + 5, + f, + proto.litrpc.OnChainBudget.serializeBinaryToWriter + ); + } + f = message.getSendToSelf(); + if (f != null) { + writer.writeMessage( + 6, + f, + proto.litrpc.SendToSelf.serializeBinaryToWriter + ); + } + f = message.getChannelRestrict(); + if (f != null) { + writer.writeMessage( + 7, + f, + proto.litrpc.ChannelRestrict.serializeBinaryToWriter + ); + } + f = message.getPeerRestrict(); + if (f != null) { + writer.writeMessage( + 8, + f, + proto.litrpc.PeerRestrict.serializeBinaryToWriter + ); + } +}; + + +/** + * optional RateLimit rate_limit = 1; + * @return {?proto.litrpc.RateLimit} + */ +proto.litrpc.RuleValue.prototype.getRateLimit = function() { + return /** @type{?proto.litrpc.RateLimit} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.RateLimit, 1)); +}; + + +/** @param {?proto.litrpc.RateLimit|undefined} value */ +proto.litrpc.RuleValue.prototype.setRateLimit = function(value) { + jspb.Message.setOneofWrapperField(this, 1, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearRateLimit = function() { + this.setRateLimit(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasRateLimit = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional ChannelPolicyBounds chan_policy_bounds = 2; + * @return {?proto.litrpc.ChannelPolicyBounds} + */ +proto.litrpc.RuleValue.prototype.getChanPolicyBounds = function() { + return /** @type{?proto.litrpc.ChannelPolicyBounds} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.ChannelPolicyBounds, 2)); +}; + + +/** @param {?proto.litrpc.ChannelPolicyBounds|undefined} value */ +proto.litrpc.RuleValue.prototype.setChanPolicyBounds = function(value) { + jspb.Message.setOneofWrapperField(this, 2, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearChanPolicyBounds = function() { + this.setChanPolicyBounds(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasChanPolicyBounds = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional HistoryLimit history_limit = 3; + * @return {?proto.litrpc.HistoryLimit} + */ +proto.litrpc.RuleValue.prototype.getHistoryLimit = function() { + return /** @type{?proto.litrpc.HistoryLimit} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.HistoryLimit, 3)); +}; + + +/** @param {?proto.litrpc.HistoryLimit|undefined} value */ +proto.litrpc.RuleValue.prototype.setHistoryLimit = function(value) { + jspb.Message.setOneofWrapperField(this, 3, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearHistoryLimit = function() { + this.setHistoryLimit(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasHistoryLimit = function() { + return jspb.Message.getField(this, 3) != null; +}; + + +/** + * optional OffChainBudget off_chain_budget = 4; + * @return {?proto.litrpc.OffChainBudget} + */ +proto.litrpc.RuleValue.prototype.getOffChainBudget = function() { + return /** @type{?proto.litrpc.OffChainBudget} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.OffChainBudget, 4)); +}; + + +/** @param {?proto.litrpc.OffChainBudget|undefined} value */ +proto.litrpc.RuleValue.prototype.setOffChainBudget = function(value) { + jspb.Message.setOneofWrapperField(this, 4, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearOffChainBudget = function() { + this.setOffChainBudget(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasOffChainBudget = function() { + return jspb.Message.getField(this, 4) != null; +}; + + +/** + * optional OnChainBudget on_chain_budget = 5; + * @return {?proto.litrpc.OnChainBudget} + */ +proto.litrpc.RuleValue.prototype.getOnChainBudget = function() { + return /** @type{?proto.litrpc.OnChainBudget} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.OnChainBudget, 5)); +}; + + +/** @param {?proto.litrpc.OnChainBudget|undefined} value */ +proto.litrpc.RuleValue.prototype.setOnChainBudget = function(value) { + jspb.Message.setOneofWrapperField(this, 5, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearOnChainBudget = function() { + this.setOnChainBudget(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasOnChainBudget = function() { + return jspb.Message.getField(this, 5) != null; +}; + + +/** + * optional SendToSelf send_to_self = 6; + * @return {?proto.litrpc.SendToSelf} + */ +proto.litrpc.RuleValue.prototype.getSendToSelf = function() { + return /** @type{?proto.litrpc.SendToSelf} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.SendToSelf, 6)); +}; + + +/** @param {?proto.litrpc.SendToSelf|undefined} value */ +proto.litrpc.RuleValue.prototype.setSendToSelf = function(value) { + jspb.Message.setOneofWrapperField(this, 6, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearSendToSelf = function() { + this.setSendToSelf(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasSendToSelf = function() { + return jspb.Message.getField(this, 6) != null; +}; + + +/** + * optional ChannelRestrict channel_restrict = 7; + * @return {?proto.litrpc.ChannelRestrict} + */ +proto.litrpc.RuleValue.prototype.getChannelRestrict = function() { + return /** @type{?proto.litrpc.ChannelRestrict} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.ChannelRestrict, 7)); +}; + + +/** @param {?proto.litrpc.ChannelRestrict|undefined} value */ +proto.litrpc.RuleValue.prototype.setChannelRestrict = function(value) { + jspb.Message.setOneofWrapperField(this, 7, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearChannelRestrict = function() { + this.setChannelRestrict(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasChannelRestrict = function() { + return jspb.Message.getField(this, 7) != null; +}; + + +/** + * optional PeerRestrict peer_restrict = 8; + * @return {?proto.litrpc.PeerRestrict} + */ +proto.litrpc.RuleValue.prototype.getPeerRestrict = function() { + return /** @type{?proto.litrpc.PeerRestrict} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.PeerRestrict, 8)); +}; + + +/** @param {?proto.litrpc.PeerRestrict|undefined} value */ +proto.litrpc.RuleValue.prototype.setPeerRestrict = function(value) { + jspb.Message.setOneofWrapperField(this, 8, proto.litrpc.RuleValue.oneofGroups_[0], value); +}; + + +proto.litrpc.RuleValue.prototype.clearPeerRestrict = function() { + this.setPeerRestrict(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RuleValue.prototype.hasPeerRestrict = function() { + return jspb.Message.getField(this, 8) != null; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.RateLimit = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.RateLimit, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.RateLimit.displayName = 'proto.litrpc.RateLimit'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.RateLimit.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.RateLimit.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.RateLimit} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RateLimit.toObject = function(includeInstance, msg) { + var f, obj = { + readLimit: (f = msg.getReadLimit()) && proto.litrpc.Rate.toObject(includeInstance, f), + writeLimit: (f = msg.getWriteLimit()) && proto.litrpc.Rate.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.RateLimit} + */ +proto.litrpc.RateLimit.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.RateLimit; + return proto.litrpc.RateLimit.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.RateLimit} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.RateLimit} + */ +proto.litrpc.RateLimit.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.litrpc.Rate; + reader.readMessage(value,proto.litrpc.Rate.deserializeBinaryFromReader); + msg.setReadLimit(value); + break; + case 2: + var value = new proto.litrpc.Rate; + reader.readMessage(value,proto.litrpc.Rate.deserializeBinaryFromReader); + msg.setWriteLimit(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.RateLimit.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.RateLimit.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.RateLimit} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.RateLimit.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getReadLimit(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.litrpc.Rate.serializeBinaryToWriter + ); + } + f = message.getWriteLimit(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.litrpc.Rate.serializeBinaryToWriter + ); + } +}; + + +/** + * optional Rate read_limit = 1; + * @return {?proto.litrpc.Rate} + */ +proto.litrpc.RateLimit.prototype.getReadLimit = function() { + return /** @type{?proto.litrpc.Rate} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.Rate, 1)); +}; + + +/** @param {?proto.litrpc.Rate|undefined} value */ +proto.litrpc.RateLimit.prototype.setReadLimit = function(value) { + jspb.Message.setWrapperField(this, 1, value); +}; + + +proto.litrpc.RateLimit.prototype.clearReadLimit = function() { + this.setReadLimit(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RateLimit.prototype.hasReadLimit = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional Rate write_limit = 2; + * @return {?proto.litrpc.Rate} + */ +proto.litrpc.RateLimit.prototype.getWriteLimit = function() { + return /** @type{?proto.litrpc.Rate} */ ( + jspb.Message.getWrapperField(this, proto.litrpc.Rate, 2)); +}; + + +/** @param {?proto.litrpc.Rate|undefined} value */ +proto.litrpc.RateLimit.prototype.setWriteLimit = function(value) { + jspb.Message.setWrapperField(this, 2, value); +}; + + +proto.litrpc.RateLimit.prototype.clearWriteLimit = function() { + this.setWriteLimit(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {!boolean} + */ +proto.litrpc.RateLimit.prototype.hasWriteLimit = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.Rate = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.Rate, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.Rate.displayName = 'proto.litrpc.Rate'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.Rate.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.Rate.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.Rate} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.Rate.toObject = function(includeInstance, msg) { + var f, obj = { + iterations: jspb.Message.getFieldWithDefault(msg, 1, 0), + numHours: jspb.Message.getFieldWithDefault(msg, 2, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.Rate} + */ +proto.litrpc.Rate.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.Rate; + return proto.litrpc.Rate.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.Rate} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.Rate} + */ +proto.litrpc.Rate.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readUint32()); + msg.setIterations(value); + break; + case 2: + var value = /** @type {number} */ (reader.readUint32()); + msg.setNumHours(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.Rate.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.Rate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.Rate} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.Rate.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getIterations(); + if (f !== 0) { + writer.writeUint32( + 1, + f + ); + } + f = message.getNumHours(); + if (f !== 0) { + writer.writeUint32( + 2, + f + ); + } +}; + + +/** + * optional uint32 iterations = 1; + * @return {number} + */ +proto.litrpc.Rate.prototype.getIterations = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.Rate.prototype.setIterations = function(value) { + jspb.Message.setProto3IntField(this, 1, value); +}; + + +/** + * optional uint32 num_hours = 2; + * @return {number} + */ +proto.litrpc.Rate.prototype.getNumHours = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 2, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.Rate.prototype.setNumHours = function(value) { + jspb.Message.setProto3IntField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.HistoryLimit = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.HistoryLimit, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.HistoryLimit.displayName = 'proto.litrpc.HistoryLimit'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.HistoryLimit.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.HistoryLimit.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.HistoryLimit} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.HistoryLimit.toObject = function(includeInstance, msg) { + var f, obj = { + startTime: jspb.Message.getFieldWithDefault(msg, 1, "0"), + duration: jspb.Message.getFieldWithDefault(msg, 2, "0") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.HistoryLimit} + */ +proto.litrpc.HistoryLimit.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.HistoryLimit; + return proto.litrpc.HistoryLimit.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.HistoryLimit} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.HistoryLimit} + */ +proto.litrpc.HistoryLimit.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setStartTime(value); + break; + case 2: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setDuration(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.HistoryLimit.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.HistoryLimit.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.HistoryLimit} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.HistoryLimit.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getStartTime(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 1, + f + ); + } + f = message.getDuration(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 2, + f + ); + } +}; + + +/** + * optional uint64 start_time = 1; + * @return {string} + */ +proto.litrpc.HistoryLimit.prototype.getStartTime = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.HistoryLimit.prototype.setStartTime = function(value) { + jspb.Message.setProto3StringIntField(this, 1, value); +}; + + +/** + * optional uint64 duration = 2; + * @return {string} + */ +proto.litrpc.HistoryLimit.prototype.getDuration = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.HistoryLimit.prototype.setDuration = function(value) { + jspb.Message.setProto3StringIntField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.ChannelPolicyBounds = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.ChannelPolicyBounds, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.ChannelPolicyBounds.displayName = 'proto.litrpc.ChannelPolicyBounds'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.ChannelPolicyBounds.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.ChannelPolicyBounds.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.ChannelPolicyBounds} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.ChannelPolicyBounds.toObject = function(includeInstance, msg) { + var f, obj = { + minBaseMsat: jspb.Message.getFieldWithDefault(msg, 1, "0"), + maxBaseMsat: jspb.Message.getFieldWithDefault(msg, 2, "0"), + minRatePpm: jspb.Message.getFieldWithDefault(msg, 3, 0), + maxRatePpm: jspb.Message.getFieldWithDefault(msg, 4, 0), + minCltvDelta: jspb.Message.getFieldWithDefault(msg, 5, 0), + maxCltvDelta: jspb.Message.getFieldWithDefault(msg, 6, 0), + minHtlcMsat: jspb.Message.getFieldWithDefault(msg, 7, "0"), + maxHtlcMsat: jspb.Message.getFieldWithDefault(msg, 8, "0") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.ChannelPolicyBounds} + */ +proto.litrpc.ChannelPolicyBounds.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.ChannelPolicyBounds; + return proto.litrpc.ChannelPolicyBounds.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.ChannelPolicyBounds} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.ChannelPolicyBounds} + */ +proto.litrpc.ChannelPolicyBounds.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMinBaseMsat(value); + break; + case 2: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMaxBaseMsat(value); + break; + case 3: + var value = /** @type {number} */ (reader.readUint32()); + msg.setMinRatePpm(value); + break; + case 4: + var value = /** @type {number} */ (reader.readUint32()); + msg.setMaxRatePpm(value); + break; + case 5: + var value = /** @type {number} */ (reader.readUint32()); + msg.setMinCltvDelta(value); + break; + case 6: + var value = /** @type {number} */ (reader.readUint32()); + msg.setMaxCltvDelta(value); + break; + case 7: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMinHtlcMsat(value); + break; + case 8: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMaxHtlcMsat(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.ChannelPolicyBounds.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.ChannelPolicyBounds.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.ChannelPolicyBounds} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.ChannelPolicyBounds.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getMinBaseMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 1, + f + ); + } + f = message.getMaxBaseMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 2, + f + ); + } + f = message.getMinRatePpm(); + if (f !== 0) { + writer.writeUint32( + 3, + f + ); + } + f = message.getMaxRatePpm(); + if (f !== 0) { + writer.writeUint32( + 4, + f + ); + } + f = message.getMinCltvDelta(); + if (f !== 0) { + writer.writeUint32( + 5, + f + ); + } + f = message.getMaxCltvDelta(); + if (f !== 0) { + writer.writeUint32( + 6, + f + ); + } + f = message.getMinHtlcMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 7, + f + ); + } + f = message.getMaxHtlcMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 8, + f + ); + } +}; + + +/** + * optional uint64 min_base_msat = 1; + * @return {string} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMinBaseMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMinBaseMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 1, value); +}; + + +/** + * optional uint64 max_base_msat = 2; + * @return {string} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMaxBaseMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMaxBaseMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 2, value); +}; + + +/** + * optional uint32 min_rate_ppm = 3; + * @return {number} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMinRatePpm = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 3, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMinRatePpm = function(value) { + jspb.Message.setProto3IntField(this, 3, value); +}; + + +/** + * optional uint32 max_rate_ppm = 4; + * @return {number} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMaxRatePpm = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMaxRatePpm = function(value) { + jspb.Message.setProto3IntField(this, 4, value); +}; + + +/** + * optional uint32 min_cltv_delta = 5; + * @return {number} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMinCltvDelta = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMinCltvDelta = function(value) { + jspb.Message.setProto3IntField(this, 5, value); +}; + + +/** + * optional uint32 max_cltv_delta = 6; + * @return {number} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMaxCltvDelta = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 6, 0)); +}; + + +/** @param {number} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMaxCltvDelta = function(value) { + jspb.Message.setProto3IntField(this, 6, value); +}; + + +/** + * optional uint64 min_htlc_msat = 7; + * @return {string} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMinHtlcMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMinHtlcMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 7, value); +}; + + +/** + * optional uint64 max_htlc_msat = 8; + * @return {string} + */ +proto.litrpc.ChannelPolicyBounds.prototype.getMaxHtlcMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.ChannelPolicyBounds.prototype.setMaxHtlcMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 8, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.OffChainBudget = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.OffChainBudget, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.OffChainBudget.displayName = 'proto.litrpc.OffChainBudget'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.OffChainBudget.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.OffChainBudget.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.OffChainBudget} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.OffChainBudget.toObject = function(includeInstance, msg) { + var f, obj = { + maxAmtMsat: jspb.Message.getFieldWithDefault(msg, 1, "0"), + maxFeesMsat: jspb.Message.getFieldWithDefault(msg, 2, "0") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.OffChainBudget} + */ +proto.litrpc.OffChainBudget.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.OffChainBudget; + return proto.litrpc.OffChainBudget.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.OffChainBudget} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.OffChainBudget} + */ +proto.litrpc.OffChainBudget.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMaxAmtMsat(value); + break; + case 2: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMaxFeesMsat(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.OffChainBudget.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.OffChainBudget.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.OffChainBudget} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.OffChainBudget.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getMaxAmtMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 1, + f + ); + } + f = message.getMaxFeesMsat(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 2, + f + ); + } +}; + + +/** + * optional uint64 max_amt_msat = 1; + * @return {string} + */ +proto.litrpc.OffChainBudget.prototype.getMaxAmtMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.OffChainBudget.prototype.setMaxAmtMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 1, value); +}; + + +/** + * optional uint64 max_fees_msat = 2; + * @return {string} + */ +proto.litrpc.OffChainBudget.prototype.getMaxFeesMsat = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.OffChainBudget.prototype.setMaxFeesMsat = function(value) { + jspb.Message.setProto3StringIntField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.OnChainBudget = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.OnChainBudget, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.OnChainBudget.displayName = 'proto.litrpc.OnChainBudget'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.OnChainBudget.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.OnChainBudget.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.OnChainBudget} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.OnChainBudget.toObject = function(includeInstance, msg) { + var f, obj = { + absoluteAmtSats: jspb.Message.getFieldWithDefault(msg, 1, "0"), + maxSatPerVByte: jspb.Message.getFieldWithDefault(msg, 2, "0") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.OnChainBudget} + */ +proto.litrpc.OnChainBudget.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.OnChainBudget; + return proto.litrpc.OnChainBudget.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.OnChainBudget} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.OnChainBudget} + */ +proto.litrpc.OnChainBudget.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setAbsoluteAmtSats(value); + break; + case 2: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setMaxSatPerVByte(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.OnChainBudget.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.OnChainBudget.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.OnChainBudget} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.OnChainBudget.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getAbsoluteAmtSats(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 1, + f + ); + } + f = message.getMaxSatPerVByte(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 2, + f + ); + } +}; + + +/** + * optional uint64 absolute_amt_sats = 1; + * @return {string} + */ +proto.litrpc.OnChainBudget.prototype.getAbsoluteAmtSats = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.OnChainBudget.prototype.setAbsoluteAmtSats = function(value) { + jspb.Message.setProto3StringIntField(this, 1, value); +}; + + +/** + * optional uint64 max_sat_per_v_byte = 2; + * @return {string} + */ +proto.litrpc.OnChainBudget.prototype.getMaxSatPerVByte = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.OnChainBudget.prototype.setMaxSatPerVByte = function(value) { + jspb.Message.setProto3StringIntField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.SendToSelf = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.litrpc.SendToSelf, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.SendToSelf.displayName = 'proto.litrpc.SendToSelf'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.SendToSelf.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.SendToSelf.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.SendToSelf} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SendToSelf.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.SendToSelf} + */ +proto.litrpc.SendToSelf.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.SendToSelf; + return proto.litrpc.SendToSelf.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.SendToSelf} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.SendToSelf} + */ +proto.litrpc.SendToSelf.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.SendToSelf.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.SendToSelf.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.SendToSelf} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.SendToSelf.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.ChannelRestrict = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.litrpc.ChannelRestrict.repeatedFields_, null); +}; +goog.inherits(proto.litrpc.ChannelRestrict, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.ChannelRestrict.displayName = 'proto.litrpc.ChannelRestrict'; +} +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.litrpc.ChannelRestrict.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.ChannelRestrict.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.ChannelRestrict.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.ChannelRestrict} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.ChannelRestrict.toObject = function(includeInstance, msg) { + var f, obj = { + channelIdsList: jspb.Message.getRepeatedField(msg, 1) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.ChannelRestrict} + */ +proto.litrpc.ChannelRestrict.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.ChannelRestrict; + return proto.litrpc.ChannelRestrict.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.ChannelRestrict} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.ChannelRestrict} + */ +proto.litrpc.ChannelRestrict.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {!Array} */ (reader.readPackedUint64String()); + msg.setChannelIdsList(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.ChannelRestrict.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.ChannelRestrict.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.ChannelRestrict} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.ChannelRestrict.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getChannelIdsList(); + if (f.length > 0) { + writer.writePackedUint64String( + 1, + f + ); + } +}; + + +/** + * repeated uint64 channel_ids = 1; + * @return {!Array} + */ +proto.litrpc.ChannelRestrict.prototype.getChannelIdsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** @param {!Array} value */ +proto.litrpc.ChannelRestrict.prototype.setChannelIdsList = function(value) { + jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {!string} value + * @param {number=} opt_index + */ +proto.litrpc.ChannelRestrict.prototype.addChannelIds = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +proto.litrpc.ChannelRestrict.prototype.clearChannelIdsList = function() { + this.setChannelIdsList([]); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.litrpc.PeerRestrict = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.litrpc.PeerRestrict.repeatedFields_, null); +}; +goog.inherits(proto.litrpc.PeerRestrict, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.litrpc.PeerRestrict.displayName = 'proto.litrpc.PeerRestrict'; +} +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.litrpc.PeerRestrict.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.litrpc.PeerRestrict.prototype.toObject = function(opt_includeInstance) { + return proto.litrpc.PeerRestrict.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.litrpc.PeerRestrict} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.PeerRestrict.toObject = function(includeInstance, msg) { + var f, obj = { + peerIdsList: jspb.Message.getRepeatedField(msg, 1) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.litrpc.PeerRestrict} + */ +proto.litrpc.PeerRestrict.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.litrpc.PeerRestrict; + return proto.litrpc.PeerRestrict.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.litrpc.PeerRestrict} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.litrpc.PeerRestrict} + */ +proto.litrpc.PeerRestrict.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.addPeerIds(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.litrpc.PeerRestrict.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.litrpc.PeerRestrict.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.litrpc.PeerRestrict} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.litrpc.PeerRestrict.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getPeerIdsList(); + if (f.length > 0) { + writer.writeRepeatedString( + 1, + f + ); + } +}; + + +/** + * repeated string peer_ids = 1; + * @return {!Array} + */ +proto.litrpc.PeerRestrict.prototype.getPeerIdsList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 1)); +}; + + +/** @param {!Array} value */ +proto.litrpc.PeerRestrict.prototype.setPeerIdsList = function(value) { + jspb.Message.setField(this, 1, value || []); +}; + + +/** + * @param {!string} value + * @param {number=} opt_index + */ +proto.litrpc.PeerRestrict.prototype.addPeerIds = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 1, value, opt_index); +}; + + +proto.litrpc.PeerRestrict.prototype.clearPeerIdsList = function() { + this.setPeerIdsList([]); +}; + + /** * @enum {number} */ @@ -2025,6 +4411,7 @@ proto.litrpc.SessionType = { TYPE_MACAROON_ADMIN: 1, TYPE_MACAROON_CUSTOM: 2, TYPE_UI_PASSWORD: 3, + TYPE_AUTOPILOT: 4, TYPE_MACAROON_ACCOUNT: 5 }; diff --git a/app/src/util/tests/sampleData.ts b/app/src/util/tests/sampleData.ts index 7f2f80179..b86761bd5 100644 --- a/app/src/util/tests/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -887,6 +887,7 @@ export const poolRegisterSidecar: POOL.SidecarTicket.AsObject = { export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionsList: [ { + id: '', devServer: true, expiryTimestampSeconds: '253370782800', label: 'Default Session', @@ -899,8 +900,73 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', accountId: '', + autopilotFeatureInfoMap: [ + [ + 'SampleFeature', + { + rulesMap: [ + [ + 'channel-policy-bounds', + { + chanPolicyBounds: { + minBaseMsat: '0', + maxBaseMsat: '10', + minRatePpm: 1, + maxRatePpm: 10, + minCltvDelta: 18, + maxCltvDelta: 18, + minHtlcMsat: '0', + maxHtlcMsat: '0', + }, + }, + ], + [ + 'channel-restriction', + { + channelRestrict: { + channelIdsList: [], + }, + }, + ], + [ + 'history-limit', + { + historyLimit: { + startTime: '0', + duration: '0', + }, + }, + ], + [ + 'peer-restriction', + { + peerRestrict: { + peerIdsList: [], + }, + }, + ], + [ + 'rate-limit', + { + rateLimit: { + readLimit: { + iterations: 50, + numHours: 1, + }, + writeLimit: { + iterations: 50, + numHours: 50, + }, + }, + }, + ], + ], + }, + ], + ], }, { + id: '', devServer: true, expiryTimestampSeconds: '253370782800', label: 'Default Session', @@ -913,6 +979,70 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', accountId: '', + autopilotFeatureInfoMap: [ + [ + 'SampleFeature', + { + rulesMap: [ + [ + 'channel-policy-bounds', + { + chanPolicyBounds: { + minBaseMsat: '0', + maxBaseMsat: '10', + minRatePpm: 1, + maxRatePpm: 10, + minCltvDelta: 18, + maxCltvDelta: 18, + minHtlcMsat: '0', + maxHtlcMsat: '0', + }, + }, + ], + [ + 'channel-restriction', + { + channelRestrict: { + channelIdsList: [], + }, + }, + ], + [ + 'history-limit', + { + historyLimit: { + startTime: '0', + duration: '0', + }, + }, + ], + [ + 'peer-restriction', + { + peerRestrict: { + peerIdsList: [], + }, + }, + ], + [ + 'rate-limit', + { + rateLimit: { + readLimit: { + iterations: 50, + numHours: 1, + }, + writeLimit: { + iterations: 50, + numHours: 50, + }, + }, + }, + ], + ], + }, + ], + ], }, ], }; diff --git a/litclient/jsoncallbacks.go b/litclient/jsoncallbacks.go index 49385d581..e13a823b9 100644 --- a/litclient/jsoncallbacks.go +++ b/litclient/jsoncallbacks.go @@ -4,6 +4,7 @@ import ( "context" "github.com/lightninglabs/faraday/frdrpc" + "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/pool/poolrpc" "github.com/lightningnetwork/lnd/lnrpc" @@ -41,4 +42,7 @@ var Registrations = []StubPackageRegistration{ looprpc.RegisterSwapClientJSONCallbacks, poolrpc.RegisterTraderJSONCallbacks, frdrpc.RegisterFaradayServerJSONCallbacks, + litrpc.RegisterSessionsJSONCallbacks, + litrpc.RegisterAccountsJSONCallbacks, + litrpc.RegisterAutopilotJSONCallbacks, } diff --git a/litrpc/Dockerfile b/litrpc/Dockerfile index 4d5ca15dd..b4c5112cd 100644 --- a/litrpc/Dockerfile +++ b/litrpc/Dockerfile @@ -12,12 +12,14 @@ ARG PROTOBUF_VERSION ARG GRPC_GATEWAY_VERSION ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0" +ENV FALAFEL_VERSION="v0.9.1" RUN cd /tmp \ && go get google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \ && go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \ && go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@${GRPC_GATEWAY_VERSION} \ - && go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} + && go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} \ + && go get github.com/lightninglabs/falafel@${FALAFEL_VERSION} WORKDIR /build diff --git a/litrpc/accounts.pb.json.go b/litrpc/accounts.pb.json.go new file mode 100644 index 000000000..5c8f724eb --- /dev/null +++ b/litrpc/accounts.pb.json.go @@ -0,0 +1,123 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: lit-accounts.proto + +package litrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterAccountsJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["litrpc.Accounts.CreateAccount"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &CreateAccountRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAccountsClient(conn) + resp, err := client.CreateAccount(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Accounts.UpdateAccount"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &UpdateAccountRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAccountsClient(conn) + resp, err := client.UpdateAccount(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Accounts.ListAccounts"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListAccountsRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAccountsClient(conn) + resp, err := client.ListAccounts(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Accounts.RemoveAccount"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &RemoveAccountRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAccountsClient(conn) + resp, err := client.RemoveAccount(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/litrpc/autopilot.pb.json.go b/litrpc/autopilot.pb.json.go new file mode 100644 index 000000000..76c915f39 --- /dev/null +++ b/litrpc/autopilot.pb.json.go @@ -0,0 +1,123 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: lit-autopilot.proto + +package litrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterAutopilotJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["litrpc.Autopilot.ListAutopilotFeatures"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListAutopilotFeaturesRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAutopilotClient(conn) + resp, err := client.ListAutopilotFeatures(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Autopilot.AddAutopilotSession"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &AddAutopilotSessionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAutopilotClient(conn) + resp, err := client.AddAutopilotSession(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Autopilot.ListAutopilotSessions"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListAutopilotSessionsRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAutopilotClient(conn) + resp, err := client.ListAutopilotSessions(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Autopilot.RevokeAutopilotSession"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &RevokeAutopilotSessionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewAutopilotClient(conn) + resp, err := client.RevokeAutopilotSession(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/litrpc/gen_protos.sh b/litrpc/gen_protos.sh index cc413d8c8..d6c6439d1 100755 --- a/litrpc/gen_protos.sh +++ b/litrpc/gen_protos.sh @@ -4,12 +4,27 @@ set -e # generate compiles the *.pb.go stubs from the *.proto files. function generate() { - # Generate the gRPC bindings for all proto files. for file in ./*.proto; do - protoc -I/usr/local/include -I. -I.. \ + # Generate the gRPC bindings for all proto files. + protoc -I. -I.. \ --go_out . --go_opt paths=source_relative \ --go-grpc_out . --go-grpc_opt paths=source_relative \ "${file}" + + # Only generate JSON/WASM stubs if requested. + if [[ "$1" == "no-wasm" ]]; then + return + fi + + # Generate the JSON/WASM autopilot stubs. + falafel=$(which falafel) + pkg="litrpc" + opts="package_name=$pkg,js_stubs=1" + protoc -I. -I.. \ + --plugin=protoc-gen-custom=$falafel\ + --custom_out=. \ + --custom_opt="$opts" \ + "${file}" done } diff --git a/litrpc/lit-autopilot.pb.go b/litrpc/lit-autopilot.pb.go new file mode 100644 index 000000000..87aafe9fd --- /dev/null +++ b/litrpc/lit-autopilot.pb.go @@ -0,0 +1,1111 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.6.1 +// source: lit-autopilot.proto + +package litrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AddAutopilotSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A human readable label to assign to the session. + Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` + // + //The unix timestamp at which this session should be revoked. + ExpiryTimestampSeconds uint64 `protobuf:"varint,2,opt,name=expiry_timestamp_seconds,json=expiryTimestampSeconds,proto3" json:"expiry_timestamp_seconds,omitempty"` + // + //The address of the mailbox server to connect to for this session. + MailboxServerAddr string `protobuf:"bytes,3,opt,name=mailbox_server_addr,json=mailboxServerAddr,proto3" json:"mailbox_server_addr,omitempty"` + // + //Set to true if tls should be skipped for when connecting to the mailbox. + DevServer bool `protobuf:"varint,4,opt,name=dev_server,json=devServer,proto3" json:"dev_server,omitempty"` + // + //The features that the session should subscribe to. Each feature maps to + //a FeatureConfig that should be applied to that feature. + Features map[string]*FeatureConfig `protobuf:"bytes,5,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // + //Rules that apply to the entire session. By default, no rules will apply + //to the entire session. + SessionRules *RulesMap `protobuf:"bytes,6,opt,name=session_rules,json=sessionRules,proto3" json:"session_rules,omitempty"` + // + //Set to true of the session should not make use of the privacy mapper. + NoPrivacyMapper bool `protobuf:"varint,7,opt,name=no_privacy_mapper,json=noPrivacyMapper,proto3" json:"no_privacy_mapper,omitempty"` +} + +func (x *AddAutopilotSessionRequest) Reset() { + *x = AddAutopilotSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddAutopilotSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddAutopilotSessionRequest) ProtoMessage() {} + +func (x *AddAutopilotSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddAutopilotSessionRequest.ProtoReflect.Descriptor instead. +func (*AddAutopilotSessionRequest) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{0} +} + +func (x *AddAutopilotSessionRequest) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *AddAutopilotSessionRequest) GetExpiryTimestampSeconds() uint64 { + if x != nil { + return x.ExpiryTimestampSeconds + } + return 0 +} + +func (x *AddAutopilotSessionRequest) GetMailboxServerAddr() string { + if x != nil { + return x.MailboxServerAddr + } + return "" +} + +func (x *AddAutopilotSessionRequest) GetDevServer() bool { + if x != nil { + return x.DevServer + } + return false +} + +func (x *AddAutopilotSessionRequest) GetFeatures() map[string]*FeatureConfig { + if x != nil { + return x.Features + } + return nil +} + +func (x *AddAutopilotSessionRequest) GetSessionRules() *RulesMap { + if x != nil { + return x.SessionRules + } + return nil +} + +func (x *AddAutopilotSessionRequest) GetNoPrivacyMapper() bool { + if x != nil { + return x.NoPrivacyMapper + } + return false +} + +type FeatureConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The RulesMap acts as an override map. In other words, by default the rules + //values recommended by the Auto Pilot server will be used but the RulesMap + //can be used to override the defaults. + Rules *RulesMap `protobuf:"bytes,1,opt,name=rules,proto3" json:"rules,omitempty"` + // + //Serialised configuration for the feature. + Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *FeatureConfig) Reset() { + *x = FeatureConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureConfig) ProtoMessage() {} + +func (x *FeatureConfig) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureConfig.ProtoReflect.Descriptor instead. +func (*FeatureConfig) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{1} +} + +func (x *FeatureConfig) GetRules() *RulesMap { + if x != nil { + return x.Rules + } + return nil +} + +func (x *FeatureConfig) GetConfig() []byte { + if x != nil { + return x.Config + } + return nil +} + +type ListAutopilotSessionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListAutopilotSessionsRequest) Reset() { + *x = ListAutopilotSessionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAutopilotSessionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAutopilotSessionsRequest) ProtoMessage() {} + +func (x *ListAutopilotSessionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAutopilotSessionsRequest.ProtoReflect.Descriptor instead. +func (*ListAutopilotSessionsRequest) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{2} +} + +type ListAutopilotSessionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A list of the Autopilot sessions. + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` +} + +func (x *ListAutopilotSessionsResponse) Reset() { + *x = ListAutopilotSessionsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAutopilotSessionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAutopilotSessionsResponse) ProtoMessage() {} + +func (x *ListAutopilotSessionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAutopilotSessionsResponse.ProtoReflect.Descriptor instead. +func (*ListAutopilotSessionsResponse) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAutopilotSessionsResponse) GetSessions() []*Session { + if x != nil { + return x.Sessions + } + return nil +} + +type AddAutopilotSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Details of the session that was just created. + Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` +} + +func (x *AddAutopilotSessionResponse) Reset() { + *x = AddAutopilotSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddAutopilotSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddAutopilotSessionResponse) ProtoMessage() {} + +func (x *AddAutopilotSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddAutopilotSessionResponse.ProtoReflect.Descriptor instead. +func (*AddAutopilotSessionResponse) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{4} +} + +func (x *AddAutopilotSessionResponse) GetSession() *Session { + if x != nil { + return x.Session + } + return nil +} + +type ListAutopilotFeaturesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListAutopilotFeaturesRequest) Reset() { + *x = ListAutopilotFeaturesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAutopilotFeaturesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAutopilotFeaturesRequest) ProtoMessage() {} + +func (x *ListAutopilotFeaturesRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAutopilotFeaturesRequest.ProtoReflect.Descriptor instead. +func (*ListAutopilotFeaturesRequest) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{5} +} + +type ListAutopilotFeaturesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A map of feature names to Feature objects describing the feature. + Features map[string]*Feature `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ListAutopilotFeaturesResponse) Reset() { + *x = ListAutopilotFeaturesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAutopilotFeaturesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAutopilotFeaturesResponse) ProtoMessage() {} + +func (x *ListAutopilotFeaturesResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAutopilotFeaturesResponse.ProtoReflect.Descriptor instead. +func (*ListAutopilotFeaturesResponse) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{6} +} + +func (x *ListAutopilotFeaturesResponse) GetFeatures() map[string]*Feature { + if x != nil { + return x.Features + } + return nil +} + +type RevokeAutopilotSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LocalPublicKey []byte `protobuf:"bytes,1,opt,name=local_public_key,json=localPublicKey,proto3" json:"local_public_key,omitempty"` +} + +func (x *RevokeAutopilotSessionRequest) Reset() { + *x = RevokeAutopilotSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeAutopilotSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeAutopilotSessionRequest) ProtoMessage() {} + +func (x *RevokeAutopilotSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeAutopilotSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeAutopilotSessionRequest) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{7} +} + +func (x *RevokeAutopilotSessionRequest) GetLocalPublicKey() []byte { + if x != nil { + return x.LocalPublicKey + } + return nil +} + +type RevokeAutopilotSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RevokeAutopilotSessionResponse) Reset() { + *x = RevokeAutopilotSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeAutopilotSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeAutopilotSessionResponse) ProtoMessage() {} + +func (x *RevokeAutopilotSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeAutopilotSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeAutopilotSessionResponse) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{8} +} + +type Feature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Name is the name of the Autopilot feature. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // + //A human readable description of what the feature offers. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // + //A map of rules that make sense for this feature. Each rule is accompanied + //with appropriate default values for the feature along with minimum and + //maximum values for the rules. + Rules map[string]*RuleValues `protobuf:"bytes,3,rep,name=rules,proto3" json:"rules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // + //A list of URI permissions required by the feature. + PermissionsList []*Permissions `protobuf:"bytes,4,rep,name=permissions_list,json=permissionsList,proto3" json:"permissions_list,omitempty"` + // + //A boolean indicating if the user would need to upgrade their Litd version in + //order to subscribe to the Autopilot feature. This will be true if the + //feature rules set contains a rule that Litd is unaware of. + RequiresUpgrade bool `protobuf:"varint,5,opt,name=requires_upgrade,json=requiresUpgrade,proto3" json:"requires_upgrade,omitempty"` +} + +func (x *Feature) Reset() { + *x = Feature{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feature) ProtoMessage() {} + +func (x *Feature) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feature.ProtoReflect.Descriptor instead. +func (*Feature) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{9} +} + +func (x *Feature) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Feature) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Feature) GetRules() map[string]*RuleValues { + if x != nil { + return x.Rules + } + return nil +} + +func (x *Feature) GetPermissionsList() []*Permissions { + if x != nil { + return x.PermissionsList + } + return nil +} + +func (x *Feature) GetRequiresUpgrade() bool { + if x != nil { + return x.RequiresUpgrade + } + return false +} + +type RuleValues struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //Whether or not the users version of Litd is aware of this rule. + Known bool `protobuf:"varint,1,opt,name=known,proto3" json:"known,omitempty"` + // + //The default values for the rule that the Autopilot server recommends for + //the associated feature. + Defaults *RuleValue `protobuf:"bytes,2,opt,name=defaults,proto3" json:"defaults,omitempty"` + // + //The minimum sane value for this rule for the associated feature. + MinValue *RuleValue `protobuf:"bytes,3,opt,name=min_value,json=minValue,proto3" json:"min_value,omitempty"` + // + //The maximum sane value for this rule for the associated feature. + MaxValue *RuleValue `protobuf:"bytes,4,opt,name=max_value,json=maxValue,proto3" json:"max_value,omitempty"` +} + +func (x *RuleValues) Reset() { + *x = RuleValues{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuleValues) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuleValues) ProtoMessage() {} + +func (x *RuleValues) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuleValues.ProtoReflect.Descriptor instead. +func (*RuleValues) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{10} +} + +func (x *RuleValues) GetKnown() bool { + if x != nil { + return x.Known + } + return false +} + +func (x *RuleValues) GetDefaults() *RuleValue { + if x != nil { + return x.Defaults + } + return nil +} + +func (x *RuleValues) GetMinValue() *RuleValue { + if x != nil { + return x.MinValue + } + return nil +} + +func (x *RuleValues) GetMaxValue() *RuleValue { + if x != nil { + return x.MaxValue + } + return nil +} + +type Permissions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The URI in question. + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + // + //A list of the permissions required for this method. + Operations []*MacaroonPermission `protobuf:"bytes,2,rep,name=operations,proto3" json:"operations,omitempty"` +} + +func (x *Permissions) Reset() { + *x = Permissions{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_autopilot_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Permissions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Permissions) ProtoMessage() {} + +func (x *Permissions) ProtoReflect() protoreflect.Message { + mi := &file_lit_autopilot_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Permissions.ProtoReflect.Descriptor instead. +func (*Permissions) Descriptor() ([]byte, []int) { + return file_lit_autopilot_proto_rawDescGZIP(), []int{11} +} + +func (x *Permissions) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *Permissions) GetOperations() []*MacaroonPermission { + if x != nil { + return x.Operations + } + return nil +} + +var File_lit_autopilot_proto protoreflect.FileDescriptor + +var file_lit_autopilot_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x6c, 0x69, 0x74, 0x2d, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x12, 0x6c, + 0x69, 0x74, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xc4, 0x03, 0x0a, 0x1a, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3c, 0x0a, 0x18, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, + 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x16, 0x65, 0x78, + 0x70, 0x69, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x41, 0x64, 0x64, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x65, 0x76, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x12, 0x4c, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x12, 0x35, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x75, 0x6c, + 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6e, 0x6f, 0x5f, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x72, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6e, 0x6f, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, + 0x70, 0x70, 0x65, 0x72, 0x1a, 0x52, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x05, 0x72, 0x75, 0x6c, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4c, 0x0a, 0x1d, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x41, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x1e, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xbe, 0x01, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x66, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4c, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x49, 0x0a, 0x1d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, + 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x20, 0x0a, + 0x1e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0xaa, 0x02, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, + 0x6c, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x10, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x0f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, + 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x1a, 0x4c, + 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x28, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb1, 0x01, 0x0a, + 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6b, + 0x6e, 0x6f, 0x77, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x12, 0x2d, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, + 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x2e, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x61, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x3a, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x32, 0xa0, 0x03, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x12, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5e, 0x0a, 0x13, 0x41, 0x64, 0x64, 0x41, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, 0x75, 0x74, 0x6f, 0x70, + 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x41, + 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x67, 0x0a, + 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, + 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x41, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, + 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, + 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_lit_autopilot_proto_rawDescOnce sync.Once + file_lit_autopilot_proto_rawDescData = file_lit_autopilot_proto_rawDesc +) + +func file_lit_autopilot_proto_rawDescGZIP() []byte { + file_lit_autopilot_proto_rawDescOnce.Do(func() { + file_lit_autopilot_proto_rawDescData = protoimpl.X.CompressGZIP(file_lit_autopilot_proto_rawDescData) + }) + return file_lit_autopilot_proto_rawDescData +} + +var file_lit_autopilot_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_lit_autopilot_proto_goTypes = []interface{}{ + (*AddAutopilotSessionRequest)(nil), // 0: litrpc.AddAutopilotSessionRequest + (*FeatureConfig)(nil), // 1: litrpc.FeatureConfig + (*ListAutopilotSessionsRequest)(nil), // 2: litrpc.ListAutopilotSessionsRequest + (*ListAutopilotSessionsResponse)(nil), // 3: litrpc.ListAutopilotSessionsResponse + (*AddAutopilotSessionResponse)(nil), // 4: litrpc.AddAutopilotSessionResponse + (*ListAutopilotFeaturesRequest)(nil), // 5: litrpc.ListAutopilotFeaturesRequest + (*ListAutopilotFeaturesResponse)(nil), // 6: litrpc.ListAutopilotFeaturesResponse + (*RevokeAutopilotSessionRequest)(nil), // 7: litrpc.RevokeAutopilotSessionRequest + (*RevokeAutopilotSessionResponse)(nil), // 8: litrpc.RevokeAutopilotSessionResponse + (*Feature)(nil), // 9: litrpc.Feature + (*RuleValues)(nil), // 10: litrpc.RuleValues + (*Permissions)(nil), // 11: litrpc.Permissions + nil, // 12: litrpc.AddAutopilotSessionRequest.FeaturesEntry + nil, // 13: litrpc.ListAutopilotFeaturesResponse.FeaturesEntry + nil, // 14: litrpc.Feature.RulesEntry + (*RulesMap)(nil), // 15: litrpc.RulesMap + (*Session)(nil), // 16: litrpc.Session + (*RuleValue)(nil), // 17: litrpc.RuleValue + (*MacaroonPermission)(nil), // 18: litrpc.MacaroonPermission +} +var file_lit_autopilot_proto_depIdxs = []int32{ + 12, // 0: litrpc.AddAutopilotSessionRequest.features:type_name -> litrpc.AddAutopilotSessionRequest.FeaturesEntry + 15, // 1: litrpc.AddAutopilotSessionRequest.session_rules:type_name -> litrpc.RulesMap + 15, // 2: litrpc.FeatureConfig.rules:type_name -> litrpc.RulesMap + 16, // 3: litrpc.ListAutopilotSessionsResponse.sessions:type_name -> litrpc.Session + 16, // 4: litrpc.AddAutopilotSessionResponse.session:type_name -> litrpc.Session + 13, // 5: litrpc.ListAutopilotFeaturesResponse.features:type_name -> litrpc.ListAutopilotFeaturesResponse.FeaturesEntry + 14, // 6: litrpc.Feature.rules:type_name -> litrpc.Feature.RulesEntry + 11, // 7: litrpc.Feature.permissions_list:type_name -> litrpc.Permissions + 17, // 8: litrpc.RuleValues.defaults:type_name -> litrpc.RuleValue + 17, // 9: litrpc.RuleValues.min_value:type_name -> litrpc.RuleValue + 17, // 10: litrpc.RuleValues.max_value:type_name -> litrpc.RuleValue + 18, // 11: litrpc.Permissions.operations:type_name -> litrpc.MacaroonPermission + 1, // 12: litrpc.AddAutopilotSessionRequest.FeaturesEntry.value:type_name -> litrpc.FeatureConfig + 9, // 13: litrpc.ListAutopilotFeaturesResponse.FeaturesEntry.value:type_name -> litrpc.Feature + 10, // 14: litrpc.Feature.RulesEntry.value:type_name -> litrpc.RuleValues + 5, // 15: litrpc.Autopilot.ListAutopilotFeatures:input_type -> litrpc.ListAutopilotFeaturesRequest + 0, // 16: litrpc.Autopilot.AddAutopilotSession:input_type -> litrpc.AddAutopilotSessionRequest + 2, // 17: litrpc.Autopilot.ListAutopilotSessions:input_type -> litrpc.ListAutopilotSessionsRequest + 7, // 18: litrpc.Autopilot.RevokeAutopilotSession:input_type -> litrpc.RevokeAutopilotSessionRequest + 6, // 19: litrpc.Autopilot.ListAutopilotFeatures:output_type -> litrpc.ListAutopilotFeaturesResponse + 4, // 20: litrpc.Autopilot.AddAutopilotSession:output_type -> litrpc.AddAutopilotSessionResponse + 3, // 21: litrpc.Autopilot.ListAutopilotSessions:output_type -> litrpc.ListAutopilotSessionsResponse + 8, // 22: litrpc.Autopilot.RevokeAutopilotSession:output_type -> litrpc.RevokeAutopilotSessionResponse + 19, // [19:23] is the sub-list for method output_type + 15, // [15:19] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name +} + +func init() { file_lit_autopilot_proto_init() } +func file_lit_autopilot_proto_init() { + if File_lit_autopilot_proto != nil { + return + } + file_lit_sessions_proto_init() + if !protoimpl.UnsafeEnabled { + file_lit_autopilot_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAutopilotSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAutopilotSessionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAutopilotSessionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddAutopilotSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAutopilotFeaturesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAutopilotFeaturesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RevokeAutopilotSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RevokeAutopilotSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuleValues); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_autopilot_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Permissions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_lit_autopilot_proto_rawDesc, + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_lit_autopilot_proto_goTypes, + DependencyIndexes: file_lit_autopilot_proto_depIdxs, + MessageInfos: file_lit_autopilot_proto_msgTypes, + }.Build() + File_lit_autopilot_proto = out.File + file_lit_autopilot_proto_rawDesc = nil + file_lit_autopilot_proto_goTypes = nil + file_lit_autopilot_proto_depIdxs = nil +} diff --git a/litrpc/lit-autopilot.proto b/litrpc/lit-autopilot.proto new file mode 100644 index 000000000..740829d5e --- /dev/null +++ b/litrpc/lit-autopilot.proto @@ -0,0 +1,174 @@ +syntax = "proto3"; + +import "lit-sessions.proto"; + +package litrpc; + +option go_package = "github.com/lightninglabs/lightning-terminal/litrpc"; + +service Autopilot { + rpc ListAutopilotFeatures (ListAutopilotFeaturesRequest) + returns (ListAutopilotFeaturesResponse); + + rpc AddAutopilotSession (AddAutopilotSessionRequest) + returns (AddAutopilotSessionResponse); + + rpc ListAutopilotSessions (ListAutopilotSessionsRequest) + returns (ListAutopilotSessionsResponse); + + rpc RevokeAutopilotSession (RevokeAutopilotSessionRequest) + returns (RevokeAutopilotSessionResponse); +} + +message AddAutopilotSessionRequest { + /* + A human readable label to assign to the session. + */ + string label = 1; + + /* + The unix timestamp at which this session should be revoked. + */ + uint64 expiry_timestamp_seconds = 2 [jstype = JS_STRING]; + + /* + The address of the mailbox server to connect to for this session. + */ + string mailbox_server_addr = 3; + + /* + Set to true if tls should be skipped for when connecting to the mailbox. + */ + bool dev_server = 4; + + /* + The features that the session should subscribe to. Each feature maps to + a FeatureConfig that should be applied to that feature. + */ + map features = 5; + + /* + Rules that apply to the entire session. By default, no rules will apply + to the entire session. + */ + RulesMap session_rules = 6; + + /* + Set to true of the session should not make use of the privacy mapper. + */ + bool no_privacy_mapper = 7; +} + +message FeatureConfig { + /* + The RulesMap acts as an override map. In other words, by default the rules + values recommended by the Auto Pilot server will be used but the RulesMap + can be used to override the defaults. + */ + RulesMap rules = 1; + + /* + Serialised configuration for the feature. + */ + bytes config = 2; +} + +message ListAutopilotSessionsRequest { +} + +message ListAutopilotSessionsResponse { + /* + A list of the Autopilot sessions. + */ + repeated Session sessions = 1; +} + +message AddAutopilotSessionResponse { + /* + Details of the session that was just created. + */ + Session session = 1; +} + +message ListAutopilotFeaturesRequest { +} + +message ListAutopilotFeaturesResponse { + /* + A map of feature names to Feature objects describing the feature. + */ + map features = 1; +} + +message RevokeAutopilotSessionRequest { + bytes local_public_key = 1; +} + +message RevokeAutopilotSessionResponse { +} + +message Feature { + /* + Name is the name of the Autopilot feature. + */ + string name = 1; + + /* + A human readable description of what the feature offers. + */ + string description = 2; + + /* + A map of rules that make sense for this feature. Each rule is accompanied + with appropriate default values for the feature along with minimum and + maximum values for the rules. + */ + map rules = 3; + + /* + A list of URI permissions required by the feature. + */ + repeated Permissions permissions_list = 4; + + /* + A boolean indicating if the user would need to upgrade their Litd version in + order to subscribe to the Autopilot feature. This will be true if the + feature rules set contains a rule that Litd is unaware of. + */ + bool requires_upgrade = 5; +} + +message RuleValues { + /* + Whether or not the users version of Litd is aware of this rule. + */ + bool known = 1; + + /* + The default values for the rule that the Autopilot server recommends for + the associated feature. + */ + RuleValue defaults = 2; + + /* + The minimum sane value for this rule for the associated feature. + */ + RuleValue min_value = 3; + + /* + The maximum sane value for this rule for the associated feature. + */ + RuleValue max_value = 4; +} + +message Permissions { + /* + The URI in question. + */ + string method = 1; + + /* + A list of the permissions required for this method. + */ + repeated MacaroonPermission operations = 2; +} \ No newline at end of file diff --git a/litrpc/lit-autopilot_grpc.pb.go b/litrpc/lit-autopilot_grpc.pb.go new file mode 100644 index 000000000..04e53f682 --- /dev/null +++ b/litrpc/lit-autopilot_grpc.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package litrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AutopilotClient is the client API for Autopilot service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AutopilotClient interface { + ListAutopilotFeatures(ctx context.Context, in *ListAutopilotFeaturesRequest, opts ...grpc.CallOption) (*ListAutopilotFeaturesResponse, error) + AddAutopilotSession(ctx context.Context, in *AddAutopilotSessionRequest, opts ...grpc.CallOption) (*AddAutopilotSessionResponse, error) + ListAutopilotSessions(ctx context.Context, in *ListAutopilotSessionsRequest, opts ...grpc.CallOption) (*ListAutopilotSessionsResponse, error) + RevokeAutopilotSession(ctx context.Context, in *RevokeAutopilotSessionRequest, opts ...grpc.CallOption) (*RevokeAutopilotSessionResponse, error) +} + +type autopilotClient struct { + cc grpc.ClientConnInterface +} + +func NewAutopilotClient(cc grpc.ClientConnInterface) AutopilotClient { + return &autopilotClient{cc} +} + +func (c *autopilotClient) ListAutopilotFeatures(ctx context.Context, in *ListAutopilotFeaturesRequest, opts ...grpc.CallOption) (*ListAutopilotFeaturesResponse, error) { + out := new(ListAutopilotFeaturesResponse) + err := c.cc.Invoke(ctx, "/litrpc.Autopilot/ListAutopilotFeatures", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) AddAutopilotSession(ctx context.Context, in *AddAutopilotSessionRequest, opts ...grpc.CallOption) (*AddAutopilotSessionResponse, error) { + out := new(AddAutopilotSessionResponse) + err := c.cc.Invoke(ctx, "/litrpc.Autopilot/AddAutopilotSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) ListAutopilotSessions(ctx context.Context, in *ListAutopilotSessionsRequest, opts ...grpc.CallOption) (*ListAutopilotSessionsResponse, error) { + out := new(ListAutopilotSessionsResponse) + err := c.cc.Invoke(ctx, "/litrpc.Autopilot/ListAutopilotSessions", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) RevokeAutopilotSession(ctx context.Context, in *RevokeAutopilotSessionRequest, opts ...grpc.CallOption) (*RevokeAutopilotSessionResponse, error) { + out := new(RevokeAutopilotSessionResponse) + err := c.cc.Invoke(ctx, "/litrpc.Autopilot/RevokeAutopilotSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AutopilotServer is the server API for Autopilot service. +// All implementations must embed UnimplementedAutopilotServer +// for forward compatibility +type AutopilotServer interface { + ListAutopilotFeatures(context.Context, *ListAutopilotFeaturesRequest) (*ListAutopilotFeaturesResponse, error) + AddAutopilotSession(context.Context, *AddAutopilotSessionRequest) (*AddAutopilotSessionResponse, error) + ListAutopilotSessions(context.Context, *ListAutopilotSessionsRequest) (*ListAutopilotSessionsResponse, error) + RevokeAutopilotSession(context.Context, *RevokeAutopilotSessionRequest) (*RevokeAutopilotSessionResponse, error) + mustEmbedUnimplementedAutopilotServer() +} + +// UnimplementedAutopilotServer must be embedded to have forward compatible implementations. +type UnimplementedAutopilotServer struct { +} + +func (UnimplementedAutopilotServer) ListAutopilotFeatures(context.Context, *ListAutopilotFeaturesRequest) (*ListAutopilotFeaturesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAutopilotFeatures not implemented") +} +func (UnimplementedAutopilotServer) AddAutopilotSession(context.Context, *AddAutopilotSessionRequest) (*AddAutopilotSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddAutopilotSession not implemented") +} +func (UnimplementedAutopilotServer) ListAutopilotSessions(context.Context, *ListAutopilotSessionsRequest) (*ListAutopilotSessionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAutopilotSessions not implemented") +} +func (UnimplementedAutopilotServer) RevokeAutopilotSession(context.Context, *RevokeAutopilotSessionRequest) (*RevokeAutopilotSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevokeAutopilotSession not implemented") +} +func (UnimplementedAutopilotServer) mustEmbedUnimplementedAutopilotServer() {} + +// UnsafeAutopilotServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AutopilotServer will +// result in compilation errors. +type UnsafeAutopilotServer interface { + mustEmbedUnimplementedAutopilotServer() +} + +func RegisterAutopilotServer(s grpc.ServiceRegistrar, srv AutopilotServer) { + s.RegisterService(&Autopilot_ServiceDesc, srv) +} + +func _Autopilot_ListAutopilotFeatures_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAutopilotFeaturesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).ListAutopilotFeatures(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Autopilot/ListAutopilotFeatures", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).ListAutopilotFeatures(ctx, req.(*ListAutopilotFeaturesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_AddAutopilotSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddAutopilotSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).AddAutopilotSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Autopilot/AddAutopilotSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).AddAutopilotSession(ctx, req.(*AddAutopilotSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_ListAutopilotSessions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAutopilotSessionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).ListAutopilotSessions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Autopilot/ListAutopilotSessions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).ListAutopilotSessions(ctx, req.(*ListAutopilotSessionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_RevokeAutopilotSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeAutopilotSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).RevokeAutopilotSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Autopilot/RevokeAutopilotSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).RevokeAutopilotSession(ctx, req.(*RevokeAutopilotSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Autopilot_ServiceDesc is the grpc.ServiceDesc for Autopilot service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Autopilot_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "litrpc.Autopilot", + HandlerType: (*AutopilotServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListAutopilotFeatures", + Handler: _Autopilot_ListAutopilotFeatures_Handler, + }, + { + MethodName: "AddAutopilotSession", + Handler: _Autopilot_AddAutopilotSession_Handler, + }, + { + MethodName: "ListAutopilotSessions", + Handler: _Autopilot_ListAutopilotSessions_Handler, + }, + { + MethodName: "RevokeAutopilotSession", + Handler: _Autopilot_RevokeAutopilotSession_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lit-autopilot.proto", +} diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index 907070f6e..57edd4b23 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -27,6 +27,7 @@ const ( SessionType_TYPE_MACAROON_ADMIN SessionType = 1 SessionType_TYPE_MACAROON_CUSTOM SessionType = 2 SessionType_TYPE_UI_PASSWORD SessionType = 3 + SessionType_TYPE_AUTOPILOT SessionType = 4 SessionType_TYPE_MACAROON_ACCOUNT SessionType = 5 ) @@ -37,6 +38,7 @@ var ( 1: "TYPE_MACAROON_ADMIN", 2: "TYPE_MACAROON_CUSTOM", 3: "TYPE_UI_PASSWORD", + 4: "TYPE_AUTOPILOT", 5: "TYPE_MACAROON_ACCOUNT", } SessionType_value = map[string]int32{ @@ -44,6 +46,7 @@ var ( "TYPE_MACAROON_ADMIN": 1, "TYPE_MACAROON_CUSTOM": 2, "TYPE_UI_PASSWORD": 3, + "TYPE_AUTOPILOT": 4, "TYPE_MACAROON_ACCOUNT": 5, } ) @@ -339,20 +342,21 @@ type Session struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id []byte `protobuf:"bytes,14,opt,name=id,proto3" json:"id,omitempty"` - Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` - SessionState SessionState `protobuf:"varint,2,opt,name=session_state,json=sessionState,proto3,enum=litrpc.SessionState" json:"session_state,omitempty"` - SessionType SessionType `protobuf:"varint,3,opt,name=session_type,json=sessionType,proto3,enum=litrpc.SessionType" json:"session_type,omitempty"` - ExpiryTimestampSeconds uint64 `protobuf:"varint,4,opt,name=expiry_timestamp_seconds,json=expiryTimestampSeconds,proto3" json:"expiry_timestamp_seconds,omitempty"` - MailboxServerAddr string `protobuf:"bytes,5,opt,name=mailbox_server_addr,json=mailboxServerAddr,proto3" json:"mailbox_server_addr,omitempty"` - DevServer bool `protobuf:"varint,6,opt,name=dev_server,json=devServer,proto3" json:"dev_server,omitempty"` - PairingSecret []byte `protobuf:"bytes,7,opt,name=pairing_secret,json=pairingSecret,proto3" json:"pairing_secret,omitempty"` - PairingSecretMnemonic string `protobuf:"bytes,8,opt,name=pairing_secret_mnemonic,json=pairingSecretMnemonic,proto3" json:"pairing_secret_mnemonic,omitempty"` - LocalPublicKey []byte `protobuf:"bytes,9,opt,name=local_public_key,json=localPublicKey,proto3" json:"local_public_key,omitempty"` - RemotePublicKey []byte `protobuf:"bytes,10,opt,name=remote_public_key,json=remotePublicKey,proto3" json:"remote_public_key,omitempty"` - CreatedAt uint64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - MacaroonRecipe *MacaroonRecipe `protobuf:"bytes,12,opt,name=macaroon_recipe,json=macaroonRecipe,proto3" json:"macaroon_recipe,omitempty"` - AccountId string `protobuf:"bytes,13,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + Id []byte `protobuf:"bytes,14,opt,name=id,proto3" json:"id,omitempty"` + Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` + SessionState SessionState `protobuf:"varint,2,opt,name=session_state,json=sessionState,proto3,enum=litrpc.SessionState" json:"session_state,omitempty"` + SessionType SessionType `protobuf:"varint,3,opt,name=session_type,json=sessionType,proto3,enum=litrpc.SessionType" json:"session_type,omitempty"` + ExpiryTimestampSeconds uint64 `protobuf:"varint,4,opt,name=expiry_timestamp_seconds,json=expiryTimestampSeconds,proto3" json:"expiry_timestamp_seconds,omitempty"` + MailboxServerAddr string `protobuf:"bytes,5,opt,name=mailbox_server_addr,json=mailboxServerAddr,proto3" json:"mailbox_server_addr,omitempty"` + DevServer bool `protobuf:"varint,6,opt,name=dev_server,json=devServer,proto3" json:"dev_server,omitempty"` + PairingSecret []byte `protobuf:"bytes,7,opt,name=pairing_secret,json=pairingSecret,proto3" json:"pairing_secret,omitempty"` + PairingSecretMnemonic string `protobuf:"bytes,8,opt,name=pairing_secret_mnemonic,json=pairingSecretMnemonic,proto3" json:"pairing_secret_mnemonic,omitempty"` + LocalPublicKey []byte `protobuf:"bytes,9,opt,name=local_public_key,json=localPublicKey,proto3" json:"local_public_key,omitempty"` + RemotePublicKey []byte `protobuf:"bytes,10,opt,name=remote_public_key,json=remotePublicKey,proto3" json:"remote_public_key,omitempty"` + CreatedAt uint64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + MacaroonRecipe *MacaroonRecipe `protobuf:"bytes,12,opt,name=macaroon_recipe,json=macaroonRecipe,proto3" json:"macaroon_recipe,omitempty"` + AccountId string `protobuf:"bytes,13,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` + AutopilotFeatureInfo map[string]*RulesMap `protobuf:"bytes,15,rep,name=autopilot_feature_info,json=autopilotFeatureInfo,proto3" json:"autopilot_feature_info,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // //The unix timestamp indicating the time at which the session was revoked. //Note that this field has not been around since the beginning and so it @@ -493,39 +497,783 @@ func (x *Session) GetAccountId() string { return "" } +func (x *Session) GetAutopilotFeatureInfo() map[string]*RulesMap { + if x != nil { + return x.AutopilotFeatureInfo + } + return nil +} + func (x *Session) GetRevokedAt() uint64 { if x != nil { - return x.RevokedAt + return x.RevokedAt + } + return 0 +} + +type MacaroonRecipe struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"` + Caveats []string `protobuf:"bytes,2,rep,name=caveats,proto3" json:"caveats,omitempty"` +} + +func (x *MacaroonRecipe) Reset() { + *x = MacaroonRecipe{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MacaroonRecipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MacaroonRecipe) ProtoMessage() {} + +func (x *MacaroonRecipe) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MacaroonRecipe.ProtoReflect.Descriptor instead. +func (*MacaroonRecipe) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{4} +} + +func (x *MacaroonRecipe) GetPermissions() []*MacaroonPermission { + if x != nil { + return x.Permissions + } + return nil +} + +func (x *MacaroonRecipe) GetCaveats() []string { + if x != nil { + return x.Caveats + } + return nil +} + +type ListSessionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListSessionsRequest) Reset() { + *x = ListSessionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSessionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsRequest) ProtoMessage() {} + +func (x *ListSessionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsRequest.ProtoReflect.Descriptor instead. +func (*ListSessionsRequest) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{5} +} + +type ListSessionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` +} + +func (x *ListSessionsResponse) Reset() { + *x = ListSessionsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSessionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsResponse) ProtoMessage() {} + +func (x *ListSessionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsResponse.ProtoReflect.Descriptor instead. +func (*ListSessionsResponse) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{6} +} + +func (x *ListSessionsResponse) GetSessions() []*Session { + if x != nil { + return x.Sessions + } + return nil +} + +type RevokeSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LocalPublicKey []byte `protobuf:"bytes,8,opt,name=local_public_key,json=localPublicKey,proto3" json:"local_public_key,omitempty"` +} + +func (x *RevokeSessionRequest) Reset() { + *x = RevokeSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSessionRequest) ProtoMessage() {} + +func (x *RevokeSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSessionRequest) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{7} +} + +func (x *RevokeSessionRequest) GetLocalPublicKey() []byte { + if x != nil { + return x.LocalPublicKey + } + return nil +} + +type RevokeSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RevokeSessionResponse) Reset() { + *x = RevokeSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSessionResponse) ProtoMessage() {} + +func (x *RevokeSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeSessionResponse) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{8} +} + +type RulesMap struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A map of rule name to RuleValue. The RuleValue should be parsed based on + //the name of the rule. + Rules map[string]*RuleValue `protobuf:"bytes,1,rep,name=rules,proto3" json:"rules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *RulesMap) Reset() { + *x = RulesMap{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RulesMap) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RulesMap) ProtoMessage() {} + +func (x *RulesMap) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RulesMap.ProtoReflect.Descriptor instead. +func (*RulesMap) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{9} +} + +func (x *RulesMap) GetRules() map[string]*RuleValue { + if x != nil { + return x.Rules + } + return nil +} + +type RuleValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Value: + // *RuleValue_RateLimit + // *RuleValue_ChanPolicyBounds + // *RuleValue_HistoryLimit + // *RuleValue_OffChainBudget + // *RuleValue_OnChainBudget + // *RuleValue_SendToSelf + // *RuleValue_ChannelRestrict + // *RuleValue_PeerRestrict + Value isRuleValue_Value `protobuf_oneof:"value"` +} + +func (x *RuleValue) Reset() { + *x = RuleValue{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RuleValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RuleValue) ProtoMessage() {} + +func (x *RuleValue) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RuleValue.ProtoReflect.Descriptor instead. +func (*RuleValue) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{10} +} + +func (m *RuleValue) GetValue() isRuleValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *RuleValue) GetRateLimit() *RateLimit { + if x, ok := x.GetValue().(*RuleValue_RateLimit); ok { + return x.RateLimit + } + return nil +} + +func (x *RuleValue) GetChanPolicyBounds() *ChannelPolicyBounds { + if x, ok := x.GetValue().(*RuleValue_ChanPolicyBounds); ok { + return x.ChanPolicyBounds + } + return nil +} + +func (x *RuleValue) GetHistoryLimit() *HistoryLimit { + if x, ok := x.GetValue().(*RuleValue_HistoryLimit); ok { + return x.HistoryLimit + } + return nil +} + +func (x *RuleValue) GetOffChainBudget() *OffChainBudget { + if x, ok := x.GetValue().(*RuleValue_OffChainBudget); ok { + return x.OffChainBudget + } + return nil +} + +func (x *RuleValue) GetOnChainBudget() *OnChainBudget { + if x, ok := x.GetValue().(*RuleValue_OnChainBudget); ok { + return x.OnChainBudget + } + return nil +} + +func (x *RuleValue) GetSendToSelf() *SendToSelf { + if x, ok := x.GetValue().(*RuleValue_SendToSelf); ok { + return x.SendToSelf + } + return nil +} + +func (x *RuleValue) GetChannelRestrict() *ChannelRestrict { + if x, ok := x.GetValue().(*RuleValue_ChannelRestrict); ok { + return x.ChannelRestrict + } + return nil +} + +func (x *RuleValue) GetPeerRestrict() *PeerRestrict { + if x, ok := x.GetValue().(*RuleValue_PeerRestrict); ok { + return x.PeerRestrict + } + return nil +} + +type isRuleValue_Value interface { + isRuleValue_Value() +} + +type RuleValue_RateLimit struct { + RateLimit *RateLimit `protobuf:"bytes,1,opt,name=rate_limit,json=rateLimit,proto3,oneof"` +} + +type RuleValue_ChanPolicyBounds struct { + ChanPolicyBounds *ChannelPolicyBounds `protobuf:"bytes,2,opt,name=chan_policy_bounds,json=chanPolicyBounds,proto3,oneof"` +} + +type RuleValue_HistoryLimit struct { + HistoryLimit *HistoryLimit `protobuf:"bytes,3,opt,name=history_limit,json=historyLimit,proto3,oneof"` +} + +type RuleValue_OffChainBudget struct { + OffChainBudget *OffChainBudget `protobuf:"bytes,4,opt,name=off_chain_budget,json=offChainBudget,proto3,oneof"` +} + +type RuleValue_OnChainBudget struct { + OnChainBudget *OnChainBudget `protobuf:"bytes,5,opt,name=on_chain_budget,json=onChainBudget,proto3,oneof"` +} + +type RuleValue_SendToSelf struct { + SendToSelf *SendToSelf `protobuf:"bytes,6,opt,name=send_to_self,json=sendToSelf,proto3,oneof"` +} + +type RuleValue_ChannelRestrict struct { + ChannelRestrict *ChannelRestrict `protobuf:"bytes,7,opt,name=channel_restrict,json=channelRestrict,proto3,oneof"` +} + +type RuleValue_PeerRestrict struct { + PeerRestrict *PeerRestrict `protobuf:"bytes,8,opt,name=peer_restrict,json=peerRestrict,proto3,oneof"` +} + +func (*RuleValue_RateLimit) isRuleValue_Value() {} + +func (*RuleValue_ChanPolicyBounds) isRuleValue_Value() {} + +func (*RuleValue_HistoryLimit) isRuleValue_Value() {} + +func (*RuleValue_OffChainBudget) isRuleValue_Value() {} + +func (*RuleValue_OnChainBudget) isRuleValue_Value() {} + +func (*RuleValue_SendToSelf) isRuleValue_Value() {} + +func (*RuleValue_ChannelRestrict) isRuleValue_Value() {} + +func (*RuleValue_PeerRestrict) isRuleValue_Value() {} + +type RateLimit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The rate limit for read-only calls. + ReadLimit *Rate `protobuf:"bytes,1,opt,name=read_limit,json=readLimit,proto3" json:"read_limit,omitempty"` + // + //The rate limit for write/execution calls. + WriteLimit *Rate `protobuf:"bytes,2,opt,name=write_limit,json=writeLimit,proto3" json:"write_limit,omitempty"` +} + +func (x *RateLimit) Reset() { + *x = RateLimit{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RateLimit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RateLimit) ProtoMessage() {} + +func (x *RateLimit) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RateLimit.ProtoReflect.Descriptor instead. +func (*RateLimit) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{11} +} + +func (x *RateLimit) GetReadLimit() *Rate { + if x != nil { + return x.ReadLimit + } + return nil +} + +func (x *RateLimit) GetWriteLimit() *Rate { + if x != nil { + return x.WriteLimit + } + return nil +} + +type Rate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The number of times a call is allowed in num_hours number of hours. + Iterations uint32 `protobuf:"varint,1,opt,name=iterations,proto3" json:"iterations,omitempty"` + // + //The number of hours in which the iterations count takes place over. + NumHours uint32 `protobuf:"varint,2,opt,name=num_hours,json=numHours,proto3" json:"num_hours,omitempty"` +} + +func (x *Rate) Reset() { + *x = Rate{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Rate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Rate) ProtoMessage() {} + +func (x *Rate) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Rate.ProtoReflect.Descriptor instead. +func (*Rate) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{12} +} + +func (x *Rate) GetIterations() uint32 { + if x != nil { + return x.Iterations + } + return 0 +} + +func (x *Rate) GetNumHours() uint32 { + if x != nil { + return x.NumHours + } + return 0 +} + +type HistoryLimit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The absolute unix timestamp in seconds before which no information should + //be shared. This should only be set if duration is not set. + StartTime uint64 `protobuf:"varint,1,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` + // + //The maximum relative duration in seconds that a request is allowed to query + //for. This should only be set if start_time is not set. + Duration uint64 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` +} + +func (x *HistoryLimit) Reset() { + *x = HistoryLimit{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HistoryLimit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryLimit) ProtoMessage() {} + +func (x *HistoryLimit) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HistoryLimit.ProtoReflect.Descriptor instead. +func (*HistoryLimit) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{13} +} + +func (x *HistoryLimit) GetStartTime() uint64 { + if x != nil { + return x.StartTime + } + return 0 +} + +func (x *HistoryLimit) GetDuration() uint64 { + if x != nil { + return x.Duration + } + return 0 +} + +type ChannelPolicyBounds struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The minimum base fee in msat that the autopilot can set for a channel. + MinBaseMsat uint64 `protobuf:"varint,1,opt,name=min_base_msat,json=minBaseMsat,proto3" json:"min_base_msat,omitempty"` + // + //The maximum base fee in msat that the autopilot can set for a channel. + MaxBaseMsat uint64 `protobuf:"varint,2,opt,name=max_base_msat,json=maxBaseMsat,proto3" json:"max_base_msat,omitempty"` + // + //The minimum ppm fee in msat that the autopilot can set for a channel. + MinRatePpm uint32 `protobuf:"varint,3,opt,name=min_rate_ppm,json=minRatePpm,proto3" json:"min_rate_ppm,omitempty"` + // + //The maximum ppm fee in msat that the autopilot can set for a channel. + MaxRatePpm uint32 `protobuf:"varint,4,opt,name=max_rate_ppm,json=maxRatePpm,proto3" json:"max_rate_ppm,omitempty"` + // + //The minimum cltv delta that the autopilot may set for a channel. + MinCltvDelta uint32 `protobuf:"varint,5,opt,name=min_cltv_delta,json=minCltvDelta,proto3" json:"min_cltv_delta,omitempty"` + // + //The maximum cltv delta that the autopilot may set for a channel. + MaxCltvDelta uint32 `protobuf:"varint,6,opt,name=max_cltv_delta,json=maxCltvDelta,proto3" json:"max_cltv_delta,omitempty"` + // + //The minimum htlc msat that the autopilot may set for a channel. + MinHtlcMsat uint64 `protobuf:"varint,7,opt,name=min_htlc_msat,json=minHtlcMsat,proto3" json:"min_htlc_msat,omitempty"` + // + //The maximum htlc msat that the autopilot may set for a channel. + MaxHtlcMsat uint64 `protobuf:"varint,8,opt,name=max_htlc_msat,json=maxHtlcMsat,proto3" json:"max_htlc_msat,omitempty"` +} + +func (x *ChannelPolicyBounds) Reset() { + *x = ChannelPolicyBounds{} + if protoimpl.UnsafeEnabled { + mi := &file_lit_sessions_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChannelPolicyBounds) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChannelPolicyBounds) ProtoMessage() {} + +func (x *ChannelPolicyBounds) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChannelPolicyBounds.ProtoReflect.Descriptor instead. +func (*ChannelPolicyBounds) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{14} +} + +func (x *ChannelPolicyBounds) GetMinBaseMsat() uint64 { + if x != nil { + return x.MinBaseMsat } return 0 } -type MacaroonRecipe struct { +func (x *ChannelPolicyBounds) GetMaxBaseMsat() uint64 { + if x != nil { + return x.MaxBaseMsat + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMinRatePpm() uint32 { + if x != nil { + return x.MinRatePpm + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMaxRatePpm() uint32 { + if x != nil { + return x.MaxRatePpm + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMinCltvDelta() uint32 { + if x != nil { + return x.MinCltvDelta + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMaxCltvDelta() uint32 { + if x != nil { + return x.MaxCltvDelta + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMinHtlcMsat() uint64 { + if x != nil { + return x.MinHtlcMsat + } + return 0 +} + +func (x *ChannelPolicyBounds) GetMaxHtlcMsat() uint64 { + if x != nil { + return x.MaxHtlcMsat + } + return 0 +} + +type OffChainBudget struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Permissions []*MacaroonPermission `protobuf:"bytes,1,rep,name=permissions,proto3" json:"permissions,omitempty"` - Caveats []string `protobuf:"bytes,2,rep,name=caveats,proto3" json:"caveats,omitempty"` + MaxAmtMsat uint64 `protobuf:"varint,1,opt,name=max_amt_msat,json=maxAmtMsat,proto3" json:"max_amt_msat,omitempty"` + MaxFeesMsat uint64 `protobuf:"varint,2,opt,name=max_fees_msat,json=maxFeesMsat,proto3" json:"max_fees_msat,omitempty"` } -func (x *MacaroonRecipe) Reset() { - *x = MacaroonRecipe{} +func (x *OffChainBudget) Reset() { + *x = OffChainBudget{} if protoimpl.UnsafeEnabled { - mi := &file_lit_sessions_proto_msgTypes[4] + mi := &file_lit_sessions_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *MacaroonRecipe) String() string { +func (x *OffChainBudget) String() string { return protoimpl.X.MessageStringOf(x) } -func (*MacaroonRecipe) ProtoMessage() {} +func (*OffChainBudget) ProtoMessage() {} -func (x *MacaroonRecipe) ProtoReflect() protoreflect.Message { - mi := &file_lit_sessions_proto_msgTypes[4] +func (x *OffChainBudget) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -536,48 +1284,51 @@ func (x *MacaroonRecipe) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use MacaroonRecipe.ProtoReflect.Descriptor instead. -func (*MacaroonRecipe) Descriptor() ([]byte, []int) { - return file_lit_sessions_proto_rawDescGZIP(), []int{4} +// Deprecated: Use OffChainBudget.ProtoReflect.Descriptor instead. +func (*OffChainBudget) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{15} } -func (x *MacaroonRecipe) GetPermissions() []*MacaroonPermission { +func (x *OffChainBudget) GetMaxAmtMsat() uint64 { if x != nil { - return x.Permissions + return x.MaxAmtMsat } - return nil + return 0 } -func (x *MacaroonRecipe) GetCaveats() []string { +func (x *OffChainBudget) GetMaxFeesMsat() uint64 { if x != nil { - return x.Caveats + return x.MaxFeesMsat } - return nil + return 0 } -type ListSessionsRequest struct { +type OnChainBudget struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + AbsoluteAmtSats uint64 `protobuf:"varint,1,opt,name=absolute_amt_sats,json=absoluteAmtSats,proto3" json:"absolute_amt_sats,omitempty"` + MaxSatPerVByte uint64 `protobuf:"varint,2,opt,name=max_sat_per_v_byte,json=maxSatPerVByte,proto3" json:"max_sat_per_v_byte,omitempty"` } -func (x *ListSessionsRequest) Reset() { - *x = ListSessionsRequest{} +func (x *OnChainBudget) Reset() { + *x = OnChainBudget{} if protoimpl.UnsafeEnabled { - mi := &file_lit_sessions_proto_msgTypes[5] + mi := &file_lit_sessions_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *ListSessionsRequest) String() string { +func (x *OnChainBudget) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSessionsRequest) ProtoMessage() {} +func (*OnChainBudget) ProtoMessage() {} -func (x *ListSessionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lit_sessions_proto_msgTypes[5] +func (x *OnChainBudget) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -588,36 +1339,48 @@ func (x *ListSessionsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSessionsRequest.ProtoReflect.Descriptor instead. -func (*ListSessionsRequest) Descriptor() ([]byte, []int) { - return file_lit_sessions_proto_rawDescGZIP(), []int{5} +// Deprecated: Use OnChainBudget.ProtoReflect.Descriptor instead. +func (*OnChainBudget) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{16} } -type ListSessionsResponse struct { +func (x *OnChainBudget) GetAbsoluteAmtSats() uint64 { + if x != nil { + return x.AbsoluteAmtSats + } + return 0 +} + +func (x *OnChainBudget) GetMaxSatPerVByte() uint64 { + if x != nil { + return x.MaxSatPerVByte + } + return 0 +} + +type SendToSelf struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` } -func (x *ListSessionsResponse) Reset() { - *x = ListSessionsResponse{} +func (x *SendToSelf) Reset() { + *x = SendToSelf{} if protoimpl.UnsafeEnabled { - mi := &file_lit_sessions_proto_msgTypes[6] + mi := &file_lit_sessions_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *ListSessionsResponse) String() string { +func (x *SendToSelf) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ListSessionsResponse) ProtoMessage() {} +func (*SendToSelf) ProtoMessage() {} -func (x *ListSessionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_lit_sessions_proto_msgTypes[6] +func (x *SendToSelf) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -628,43 +1391,36 @@ func (x *ListSessionsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ListSessionsResponse.ProtoReflect.Descriptor instead. -func (*ListSessionsResponse) Descriptor() ([]byte, []int) { - return file_lit_sessions_proto_rawDescGZIP(), []int{6} -} - -func (x *ListSessionsResponse) GetSessions() []*Session { - if x != nil { - return x.Sessions - } - return nil +// Deprecated: Use SendToSelf.ProtoReflect.Descriptor instead. +func (*SendToSelf) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{17} } -type RevokeSessionRequest struct { +type ChannelRestrict struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - LocalPublicKey []byte `protobuf:"bytes,8,opt,name=local_public_key,json=localPublicKey,proto3" json:"local_public_key,omitempty"` + ChannelIds []uint64 `protobuf:"varint,1,rep,packed,name=channel_ids,json=channelIds,proto3" json:"channel_ids,omitempty"` } -func (x *RevokeSessionRequest) Reset() { - *x = RevokeSessionRequest{} +func (x *ChannelRestrict) Reset() { + *x = ChannelRestrict{} if protoimpl.UnsafeEnabled { - mi := &file_lit_sessions_proto_msgTypes[7] + mi := &file_lit_sessions_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *RevokeSessionRequest) String() string { +func (x *ChannelRestrict) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RevokeSessionRequest) ProtoMessage() {} +func (*ChannelRestrict) ProtoMessage() {} -func (x *RevokeSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_lit_sessions_proto_msgTypes[7] +func (x *ChannelRestrict) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -675,41 +1431,43 @@ func (x *RevokeSessionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RevokeSessionRequest.ProtoReflect.Descriptor instead. -func (*RevokeSessionRequest) Descriptor() ([]byte, []int) { - return file_lit_sessions_proto_rawDescGZIP(), []int{7} +// Deprecated: Use ChannelRestrict.ProtoReflect.Descriptor instead. +func (*ChannelRestrict) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{18} } -func (x *RevokeSessionRequest) GetLocalPublicKey() []byte { +func (x *ChannelRestrict) GetChannelIds() []uint64 { if x != nil { - return x.LocalPublicKey + return x.ChannelIds } return nil } -type RevokeSessionResponse struct { +type PeerRestrict struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + PeerIds []string `protobuf:"bytes,1,rep,name=peer_ids,json=peerIds,proto3" json:"peer_ids,omitempty"` } -func (x *RevokeSessionResponse) Reset() { - *x = RevokeSessionResponse{} +func (x *PeerRestrict) Reset() { + *x = PeerRestrict{} if protoimpl.UnsafeEnabled { - mi := &file_lit_sessions_proto_msgTypes[8] + mi := &file_lit_sessions_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *RevokeSessionResponse) String() string { +func (x *PeerRestrict) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RevokeSessionResponse) ProtoMessage() {} +func (*PeerRestrict) ProtoMessage() {} -func (x *RevokeSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_lit_sessions_proto_msgTypes[8] +func (x *PeerRestrict) ProtoReflect() protoreflect.Message { + mi := &file_lit_sessions_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -720,9 +1478,16 @@ func (x *RevokeSessionResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RevokeSessionResponse.ProtoReflect.Descriptor instead. -func (*RevokeSessionResponse) Descriptor() ([]byte, []int) { - return file_lit_sessions_proto_rawDescGZIP(), []int{8} +// Deprecated: Use PeerRestrict.ProtoReflect.Descriptor instead. +func (*PeerRestrict) Descriptor() ([]byte, []int) { + return file_lit_sessions_proto_rawDescGZIP(), []int{19} +} + +func (x *PeerRestrict) GetPeerIds() []string { + if x != nil { + return x.PeerIds + } + return nil } var File_lit_sessions_proto protoreflect.FileDescriptor @@ -761,7 +1526,7 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, - 0x90, 0x05, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0xc6, 0x06, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x39, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, @@ -799,61 +1564,172 @@ var file_lit_sessions_proto_rawDesc = []byte{ 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x65, 0x52, 0x0e, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0a, 0x72, 0x65, 0x76, - 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, - 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x0f, - 0x10, 0x10, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, - 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, 0x74, 0x73, 0x22, 0x15, 0x0a, 0x13, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x2a, 0x93, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, - 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x00, 0x12, - 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, - 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x43, 0x55, 0x53, 0x54, 0x4f, 0x4d, - 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x5f, 0x50, 0x41, - 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, - 0x54, 0x10, 0x05, 0x22, 0x04, 0x08, 0x04, 0x10, 0x04, 0x2a, 0x59, 0x0a, 0x0c, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, - 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, 0x11, - 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, 0x10, - 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, - 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, - 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x5f, 0x0a, 0x16, 0x61, 0x75, 0x74, + 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6c, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x70, + 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0a, 0x72, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x1a, 0x59, 0x0a, + 0x19, 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x68, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x69, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, + 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x61, 0x76, 0x65, + 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x76, 0x65, 0x61, + 0x74, 0x73, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x43, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, - 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, - 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, - 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, - 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, - 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x40, + 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, + 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x8a, 0x01, 0x0a, 0x08, 0x52, 0x75, + 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0a, 0x52, 0x75, 0x6c, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x92, 0x04, 0x0a, 0x09, 0x52, 0x75, 0x6c, 0x65, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x32, 0x0a, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x48, 0x00, 0x52, 0x09, 0x72, + 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x4b, 0x0a, 0x12, 0x63, 0x68, 0x61, 0x6e, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, 0x75, 0x6e, 0x64, + 0x73, 0x48, 0x00, 0x52, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, + 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x3b, 0x0a, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x42, 0x0a, 0x10, 0x6f, 0x66, 0x66, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, 0x75, + 0x64, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x3f, 0x0a, 0x0f, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x5f, 0x62, 0x75, 0x64, 0x67, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x36, 0x0a, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x5f, + 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, + 0x66, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x12, + 0x44, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6c, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x3b, 0x0a, 0x0d, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x72, 0x65, + 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, + 0x63, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x67, 0x0a, 0x09, 0x52, + 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2b, 0x0a, 0x0a, 0x72, 0x65, 0x61, 0x64, + 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x09, 0x72, 0x65, 0x61, 0x64, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x0b, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x77, 0x72, 0x69, 0x74, 0x65, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x22, 0x43, 0x0a, 0x04, 0x52, 0x61, 0x74, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0a, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, + 0x6e, 0x75, 0x6d, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x08, 0x6e, 0x75, 0x6d, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x0c, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x21, 0x0a, 0x0a, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, + 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x08, + 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, + 0x30, 0x01, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xc5, 0x02, 0x0a, + 0x13, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x6f, + 0x75, 0x6e, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x0b, 0x6d, 0x69, 0x6e, 0x42, 0x61, 0x73, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, + 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x42, 0x61, 0x73, 0x65, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x61, 0x74, 0x65, + 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x69, 0x6e, 0x52, + 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x61, + 0x74, 0x65, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6d, 0x61, + 0x78, 0x52, 0x61, 0x74, 0x65, 0x50, 0x70, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x69, 0x6e, 0x5f, + 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0c, 0x6d, 0x69, 0x6e, 0x43, 0x6c, 0x74, 0x76, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, + 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x43, 0x6c, 0x74, 0x76, 0x44, + 0x65, 0x6c, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x0b, 0x6d, 0x69, 0x6e, 0x48, 0x74, 0x6c, 0x63, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, + 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x74, 0x6c, 0x63, + 0x4d, 0x73, 0x61, 0x74, 0x22, 0x5e, 0x0a, 0x0e, 0x4f, 0x66, 0x66, 0x43, 0x68, 0x61, 0x69, 0x6e, + 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x24, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x6d, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, + 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x41, 0x6d, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x26, 0x0a, 0x0d, + 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x73, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x73, + 0x4d, 0x73, 0x61, 0x74, 0x22, 0x6f, 0x0a, 0x0d, 0x4f, 0x6e, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, + 0x75, 0x64, 0x67, 0x65, 0x74, 0x12, 0x2e, 0x0a, 0x11, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x65, 0x5f, 0x61, 0x6d, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x42, 0x02, 0x30, 0x01, 0x52, 0x0f, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x41, 0x6d, + 0x74, 0x53, 0x61, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x61, 0x74, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, + 0x56, 0x42, 0x79, 0x74, 0x65, 0x22, 0x0c, 0x0a, 0x0a, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x53, + 0x65, 0x6c, 0x66, 0x22, 0x36, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x73, 0x22, 0x29, 0x0a, 0x0c, 0x50, + 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, + 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x65, 0x65, 0x72, 0x49, 0x64, 0x73, 0x2a, 0xa1, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, + 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x4f, 0x4e, 0x4c, 0x59, + 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, + 0x4f, 0x4f, 0x4e, 0x5f, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, 0x5f, 0x43, 0x55, 0x53, + 0x54, 0x4f, 0x4d, 0x10, 0x02, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, + 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x41, 0x55, 0x54, 0x4f, 0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x04, 0x12, + 0x19, 0x0a, 0x15, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x41, 0x43, 0x41, 0x52, 0x4f, 0x4f, 0x4e, + 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x05, 0x2a, 0x59, 0x0a, 0x0c, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, + 0x41, 0x54, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, + 0x0c, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x49, 0x4e, 0x5f, 0x55, 0x53, 0x45, 0x10, 0x01, 0x12, + 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 0x45, 0x44, + 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x58, 0x50, 0x49, + 0x52, 0x45, 0x44, 0x10, 0x03, 0x32, 0xe8, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x43, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x19, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x69, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, + 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, + 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -869,7 +1745,7 @@ func file_lit_sessions_proto_rawDescGZIP() []byte { } var file_lit_sessions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_lit_sessions_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_lit_sessions_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_lit_sessions_proto_goTypes = []interface{}{ (SessionType)(0), // 0: litrpc.SessionType (SessionState)(0), // 1: litrpc.SessionState @@ -882,6 +1758,19 @@ var file_lit_sessions_proto_goTypes = []interface{}{ (*ListSessionsResponse)(nil), // 8: litrpc.ListSessionsResponse (*RevokeSessionRequest)(nil), // 9: litrpc.RevokeSessionRequest (*RevokeSessionResponse)(nil), // 10: litrpc.RevokeSessionResponse + (*RulesMap)(nil), // 11: litrpc.RulesMap + (*RuleValue)(nil), // 12: litrpc.RuleValue + (*RateLimit)(nil), // 13: litrpc.RateLimit + (*Rate)(nil), // 14: litrpc.Rate + (*HistoryLimit)(nil), // 15: litrpc.HistoryLimit + (*ChannelPolicyBounds)(nil), // 16: litrpc.ChannelPolicyBounds + (*OffChainBudget)(nil), // 17: litrpc.OffChainBudget + (*OnChainBudget)(nil), // 18: litrpc.OnChainBudget + (*SendToSelf)(nil), // 19: litrpc.SendToSelf + (*ChannelRestrict)(nil), // 20: litrpc.ChannelRestrict + (*PeerRestrict)(nil), // 21: litrpc.PeerRestrict + nil, // 22: litrpc.Session.AutopilotFeatureInfoEntry + nil, // 23: litrpc.RulesMap.RulesEntry } var file_lit_sessions_proto_depIdxs = []int32{ 0, // 0: litrpc.AddSessionRequest.session_type:type_name -> litrpc.SessionType @@ -890,19 +1779,33 @@ var file_lit_sessions_proto_depIdxs = []int32{ 1, // 3: litrpc.Session.session_state:type_name -> litrpc.SessionState 0, // 4: litrpc.Session.session_type:type_name -> litrpc.SessionType 6, // 5: litrpc.Session.macaroon_recipe:type_name -> litrpc.MacaroonRecipe - 3, // 6: litrpc.MacaroonRecipe.permissions:type_name -> litrpc.MacaroonPermission - 5, // 7: litrpc.ListSessionsResponse.sessions:type_name -> litrpc.Session - 2, // 8: litrpc.Sessions.AddSession:input_type -> litrpc.AddSessionRequest - 7, // 9: litrpc.Sessions.ListSessions:input_type -> litrpc.ListSessionsRequest - 9, // 10: litrpc.Sessions.RevokeSession:input_type -> litrpc.RevokeSessionRequest - 4, // 11: litrpc.Sessions.AddSession:output_type -> litrpc.AddSessionResponse - 8, // 12: litrpc.Sessions.ListSessions:output_type -> litrpc.ListSessionsResponse - 10, // 13: litrpc.Sessions.RevokeSession:output_type -> litrpc.RevokeSessionResponse - 11, // [11:14] is the sub-list for method output_type - 8, // [8:11] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 22, // 6: litrpc.Session.autopilot_feature_info:type_name -> litrpc.Session.AutopilotFeatureInfoEntry + 3, // 7: litrpc.MacaroonRecipe.permissions:type_name -> litrpc.MacaroonPermission + 5, // 8: litrpc.ListSessionsResponse.sessions:type_name -> litrpc.Session + 23, // 9: litrpc.RulesMap.rules:type_name -> litrpc.RulesMap.RulesEntry + 13, // 10: litrpc.RuleValue.rate_limit:type_name -> litrpc.RateLimit + 16, // 11: litrpc.RuleValue.chan_policy_bounds:type_name -> litrpc.ChannelPolicyBounds + 15, // 12: litrpc.RuleValue.history_limit:type_name -> litrpc.HistoryLimit + 17, // 13: litrpc.RuleValue.off_chain_budget:type_name -> litrpc.OffChainBudget + 18, // 14: litrpc.RuleValue.on_chain_budget:type_name -> litrpc.OnChainBudget + 19, // 15: litrpc.RuleValue.send_to_self:type_name -> litrpc.SendToSelf + 20, // 16: litrpc.RuleValue.channel_restrict:type_name -> litrpc.ChannelRestrict + 21, // 17: litrpc.RuleValue.peer_restrict:type_name -> litrpc.PeerRestrict + 14, // 18: litrpc.RateLimit.read_limit:type_name -> litrpc.Rate + 14, // 19: litrpc.RateLimit.write_limit:type_name -> litrpc.Rate + 11, // 20: litrpc.Session.AutopilotFeatureInfoEntry.value:type_name -> litrpc.RulesMap + 12, // 21: litrpc.RulesMap.RulesEntry.value:type_name -> litrpc.RuleValue + 2, // 22: litrpc.Sessions.AddSession:input_type -> litrpc.AddSessionRequest + 7, // 23: litrpc.Sessions.ListSessions:input_type -> litrpc.ListSessionsRequest + 9, // 24: litrpc.Sessions.RevokeSession:input_type -> litrpc.RevokeSessionRequest + 4, // 25: litrpc.Sessions.AddSession:output_type -> litrpc.AddSessionResponse + 8, // 26: litrpc.Sessions.ListSessions:output_type -> litrpc.ListSessionsResponse + 10, // 27: litrpc.Sessions.RevokeSession:output_type -> litrpc.RevokeSessionResponse + 25, // [25:28] is the sub-list for method output_type + 22, // [22:25] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_lit_sessions_proto_init() } @@ -1019,6 +1922,148 @@ func file_lit_sessions_proto_init() { return nil } } + file_lit_sessions_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RulesMap); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RuleValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RateLimit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Rate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HistoryLimit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelPolicyBounds); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OffChainBudget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OnChainBudget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendToSelf); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelRestrict); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lit_sessions_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerRestrict); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_lit_sessions_proto_msgTypes[10].OneofWrappers = []interface{}{ + (*RuleValue_RateLimit)(nil), + (*RuleValue_ChanPolicyBounds)(nil), + (*RuleValue_HistoryLimit)(nil), + (*RuleValue_OffChainBudget)(nil), + (*RuleValue_OnChainBudget)(nil), + (*RuleValue_SendToSelf)(nil), + (*RuleValue_ChannelRestrict)(nil), + (*RuleValue_PeerRestrict)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1026,7 +2071,7 @@ func file_lit_sessions_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lit_sessions_proto_rawDesc, NumEnums: 2, - NumMessages: 9, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/litrpc/lit-sessions.proto b/litrpc/lit-sessions.proto index 21d988783..27737f332 100644 --- a/litrpc/lit-sessions.proto +++ b/litrpc/lit-sessions.proto @@ -19,7 +19,7 @@ enum SessionType { TYPE_MACAROON_ADMIN = 1; TYPE_MACAROON_CUSTOM = 2; TYPE_UI_PASSWORD = 3; - reserved 4; + TYPE_AUTOPILOT = 4; TYPE_MACAROON_ACCOUNT = 5; } @@ -95,10 +95,7 @@ message Session { string account_id = 13; - /* - Field number 15 is reserved for future use. - */ - reserved 15; + map autopilot_feature_info = 15; /* The unix timestamp indicating the time at which the session was revoked. @@ -130,3 +127,125 @@ message RevokeSessionRequest { message RevokeSessionResponse { } + +message RulesMap { + /* + A map of rule name to RuleValue. The RuleValue should be parsed based on + the name of the rule. + */ + map rules = 1; +} + +message RuleValue { + oneof value { + RateLimit rate_limit = 1; + ChannelPolicyBounds chan_policy_bounds = 2; + HistoryLimit history_limit = 3; + OffChainBudget off_chain_budget = 4; + OnChainBudget on_chain_budget = 5; + SendToSelf send_to_self = 6; + ChannelRestrict channel_restrict = 7; + PeerRestrict peer_restrict = 8; + } +} + +message RateLimit { + /* + The rate limit for read-only calls. + */ + Rate read_limit = 1; + + /* + The rate limit for write/execution calls. + */ + Rate write_limit = 2; +} + +message Rate { + /* + The number of times a call is allowed in num_hours number of hours. + */ + uint32 iterations = 1; + + /* + The number of hours in which the iterations count takes place over. + */ + uint32 num_hours = 2; +} + +message HistoryLimit { + /* + The absolute unix timestamp in seconds before which no information should + be shared. This should only be set if duration is not set. + */ + uint64 start_time = 1 [jstype = JS_STRING]; + + /* + The maximum relative duration in seconds that a request is allowed to query + for. This should only be set if start_time is not set. + */ + uint64 duration = 2 [jstype = JS_STRING]; +} + +message ChannelPolicyBounds { + /* + The minimum base fee in msat that the autopilot can set for a channel. + */ + uint64 min_base_msat = 1 [jstype = JS_STRING]; + + /* + The maximum base fee in msat that the autopilot can set for a channel. + */ + uint64 max_base_msat = 2 [jstype = JS_STRING]; + + /* + The minimum ppm fee in msat that the autopilot can set for a channel. + */ + uint32 min_rate_ppm = 3; + + /* + The maximum ppm fee in msat that the autopilot can set for a channel. + */ + uint32 max_rate_ppm = 4; + + /* + The minimum cltv delta that the autopilot may set for a channel. + */ + uint32 min_cltv_delta = 5; + + /* + The maximum cltv delta that the autopilot may set for a channel. + */ + uint32 max_cltv_delta = 6; + + /* + The minimum htlc msat that the autopilot may set for a channel. + */ + uint64 min_htlc_msat = 7 [jstype = JS_STRING]; + + /* + The maximum htlc msat that the autopilot may set for a channel. + */ + uint64 max_htlc_msat = 8 [jstype = JS_STRING]; +} + +message OffChainBudget { + uint64 max_amt_msat = 1 [jstype = JS_STRING]; + uint64 max_fees_msat = 2 [jstype = JS_STRING]; +} + +message OnChainBudget { + uint64 absolute_amt_sats = 1 [jstype = JS_STRING]; + uint64 max_sat_per_v_byte = 2 [jstype = JS_STRING]; +} + +message SendToSelf { +} + +message ChannelRestrict { + repeated uint64 channel_ids = 1 [jstype = JS_STRING]; +} + +message PeerRestrict { + repeated string peer_ids = 1; +} \ No newline at end of file diff --git a/litrpc/sessions.pb.json.go b/litrpc/sessions.pb.json.go new file mode 100644 index 000000000..c49bd96d6 --- /dev/null +++ b/litrpc/sessions.pb.json.go @@ -0,0 +1,98 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: lit-sessions.proto + +package litrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterSessionsJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["litrpc.Sessions.AddSession"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &AddSessionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSessionsClient(conn) + resp, err := client.AddSession(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Sessions.ListSessions"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListSessionsRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSessionsClient(conn) + resp, err := client.ListSessions(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + + registry["litrpc.Sessions.RevokeSession"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &RevokeSessionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSessionsClient(conn) + resp, err := client.RevokeSession(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} From eaffa4a74a0061b191bfd97abbbd7be49c302c12 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 13:50:08 +0200 Subject: [PATCH 09/47] firewalldb+log: add main DB structure --- firewalldb/db.go | 112 ++++++++++++++++++++++++++++++++++++++ firewalldb/log.go | 25 +++++++++ firewalldb/metadata.go | 121 +++++++++++++++++++++++++++++++++++++++++ log.go | 4 ++ 4 files changed, 262 insertions(+) create mode 100644 firewalldb/db.go create mode 100644 firewalldb/log.go create mode 100644 firewalldb/metadata.go diff --git a/firewalldb/db.go b/firewalldb/db.go new file mode 100644 index 000000000..e0e7f81bb --- /dev/null +++ b/firewalldb/db.go @@ -0,0 +1,112 @@ +package firewalldb + +import ( + "encoding/binary" + "fmt" + "os" + "path/filepath" + "time" + + "go.etcd.io/bbolt" +) + +const ( + // DBFilename is the default filename of the rules' database. + DBFilename = "rules.db" + + // dbFilePermission is the default permission the rules' database file + // is created with. + dbFilePermission = 0600 + + // DefaultRulesDBTimeout is the default maximum time we wait for the + // db bbolt database to be opened. If the database is already + // opened by another process, the unique lock cannot be obtained. With + // the timeout we error out after the given time instead of just + // blocking for forever. + DefaultRulesDBTimeout = 5 * time.Second +) + +var ( + // byteOrder is the default byte order we'll use for serialization + // within the database. + byteOrder = binary.BigEndian +) + +// DB is a bolt-backed persistent store. +type DB struct { + *bbolt.DB +} + +// NewDB creates a new bolt database that can be found at the given directory. +func NewDB(dir, fileName string) (*DB, error) { + firstInit := false + path := filepath.Join(dir, fileName) + + // If the database file does not exist yet, create its directory. + if !fileExists(path) { + if err := os.MkdirAll(dir, 0700); err != nil { + return nil, err + } + firstInit = true + } + + db, err := initDB(path, firstInit) + if err != nil { + return nil, err + } + + // Attempt to sync the database's current version with the latest known + // version available. + if err := syncVersions(db); err != nil { + return nil, err + } + + return &DB{DB: db}, nil +} + +// fileExists reports whether the named file or directory exists. +func fileExists(path string) bool { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} + +// initDB initializes all the required top-level buckets for the database. +func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { + db, err := bbolt.Open(filepath, dbFilePermission, &bbolt.Options{ + Timeout: DefaultRulesDBTimeout, + }) + if err == bbolt.ErrTimeout { + return nil, fmt.Errorf("error while trying to open %s: timed "+ + "out after %v when trying to obtain exclusive lock", + filepath, DefaultRulesDBTimeout) + } + if err != nil { + return nil, err + } + + err = db.Update(func(tx *bbolt.Tx) error { + if firstInit { + metadataBucket, err := tx.CreateBucketIfNotExists( + metadataBucketKey, + ) + if err != nil { + return err + } + err = setDBVersion(metadataBucket, latestDBVersion) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, err + } + + return db, nil +} diff --git a/firewalldb/log.go b/firewalldb/log.go new file mode 100644 index 000000000..48fbb13af --- /dev/null +++ b/firewalldb/log.go @@ -0,0 +1,25 @@ +package firewalldb + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "FWDB" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/firewalldb/metadata.go b/firewalldb/metadata.go new file mode 100644 index 000000000..ff9438e9f --- /dev/null +++ b/firewalldb/metadata.go @@ -0,0 +1,121 @@ +package firewalldb + +import ( + "errors" + "fmt" + + "go.etcd.io/bbolt" +) + +// migration is a function which takes a prior outdated version of the database +// instance and mutates the key/bucket structure to arrive at a more up-to-date +// version of the database. +type migration func(tx *bbolt.Tx) error + +var ( + // metadataBucketKey stores all the metadata concerning the state of the + // database. + metadataBucketKey = []byte("metadata") + + // dbVersionKey is the key used for storing/retrieving the current + // database version. + dbVersionKey = []byte("version") + + // ErrDBReversion is returned when detecting an attempt to revert to a + // prior database version. + ErrDBReversion = errors.New("cannot revert to prior version") + + // dbVersions is storing all versions of database. If the current + // version of the database doesn't match the latest version this list + // will be used for retrieving all migration function that are need to + // apply to the current db. + dbVersions []migration + + latestDBVersion = uint32(len(dbVersions)) +) + +// getDBVersion retrieves the current database version. +func getDBVersion(bucket *bbolt.Bucket) (uint32, error) { + versionBytes := bucket.Get(dbVersionKey) + if versionBytes == nil { + return 0, errors.New("database version not found") + } + return byteOrder.Uint32(versionBytes), nil +} + +// setDBVersion updates the current database version. +func setDBVersion(bucket *bbolt.Bucket, version uint32) error { + var b [4]byte + byteOrder.PutUint32(b[:], version) + return bucket.Put(dbVersionKey, b[:]) +} + +// getBucket retrieves the bucket with the given key. +func getBucket(tx *bbolt.Tx, key []byte) (*bbolt.Bucket, error) { + bucket := tx.Bucket(key) + if bucket == nil { + return nil, fmt.Errorf("bucket \"%v\" does not exist", + string(key)) + } + return bucket, nil +} + +// syncVersions function is used for safe db version synchronization. It +// applies migration functions to the current database and recovers the +// previous state of db if at least one error/panic appeared during migration. +func syncVersions(db *bbolt.DB) error { + var currentVersion uint32 + err := db.View(func(tx *bbolt.Tx) error { + metadata, err := getBucket(tx, metadataBucketKey) + if err != nil { + return err + } + currentVersion, err = getDBVersion(metadata) + return err + }) + if err != nil { + return err + } + + log.Infof("Checking for schema update: latest_version=%v, "+ + "db_version=%v", latestDBVersion, currentVersion) + + switch { + // If the database reports a higher version that we are aware of, the + // user is probably trying to revert to a prior version of lnd. We fail + // here to prevent reversions and unintended corruption. + case currentVersion > latestDBVersion: + log.Errorf("Refusing to revert from db_version=%d to "+ + "lower version=%d", currentVersion, + latestDBVersion) + + return ErrDBReversion + + // If the current database version matches the latest version number, + // then we don't need to perform any migrations. + case currentVersion == latestDBVersion: + return nil + } + + log.Infof("Performing database schema migration") + + // Otherwise, we execute the migrations serially within a single + // database transaction to ensure the migration is atomic. + return db.Update(func(tx *bbolt.Tx) error { + for v := currentVersion; v < latestDBVersion; v++ { + log.Infof("Applying migration #%v", v+1) + + migration := dbVersions[v] + if err := migration(tx); err != nil { + log.Infof("Unable to apply migration #%v", v+1) + return err + } + } + + metadata, err := getBucket(tx, metadataBucketKey) + if err != nil { + return err + } + return setDBVersion(metadata, latestDBVersion) + }) +} diff --git a/log.go b/log.go index db679a4c9..cb8a4cb0a 100644 --- a/log.go +++ b/log.go @@ -6,6 +6,7 @@ import ( "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" "github.com/lightninglabs/lightning-terminal/firewall" + "github.com/lightninglabs/lightning-terminal/firewalldb" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightninglabs/loop/loopd" @@ -69,6 +70,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { lnd.AddSubLogger( root, firewall.Subsystem, intercept, firewall.UseLogger, ) + lnd.AddSubLogger( + root, firewalldb.Subsystem, intercept, firewalldb.UseLogger, + ) // Add daemon loggers to lnd's root logger. faraday.SetupLoggers(root, intercept) From 704acdf504c8831a1b29cf1c964bb3357b35c74e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 13:51:42 +0200 Subject: [PATCH 10/47] firewalldb: add Action CRUD logic This commit adds a new Action type to the firewalldb package along with Serialization and Deserialization methods for it. It also adds ListAction and ListSessionAction methods to the DB which can be used to paginate through the actions. Various interfaces are also added that will be used by `rules` to access certain RuleActions. --- firewalldb/action_paginator.go | 250 ++++++++++++ firewalldb/actions.go | 677 +++++++++++++++++++++++++++++++++ firewalldb/actions_test.go | 337 ++++++++++++++++ firewalldb/db.go | 19 +- 4 files changed, 1282 insertions(+), 1 deletion(-) create mode 100644 firewalldb/action_paginator.go create mode 100644 firewalldb/actions.go create mode 100644 firewalldb/actions_test.go diff --git a/firewalldb/action_paginator.go b/firewalldb/action_paginator.go new file mode 100644 index 000000000..5e492f55e --- /dev/null +++ b/firewalldb/action_paginator.go @@ -0,0 +1,250 @@ +package firewalldb + +import ( + "encoding/binary" + + "github.com/lightningnetwork/lnd/kvdb" +) + +type actionPaginator struct { + // cursor is the cursor which we are using to iterate through a bucket. + cursor kvdb.RCursor + + // cfg is the query config which we are using to determine how to + // iterate over the data. + cfg *ListActionsQuery + + // filterFn is the filter function which we are using to determine which + // actions should be included in the return list. + filterFn ListActionsFilterFn + + // readAction is a closure which we use to read an action from the db + // given a key value pair. + readAction func(k, v []byte) (*Action, error) +} + +// paginateActions paginates through the set of actions in the database. It +// uses the provided cursor to determine which keys to iterate over, it uses the +// provided query options to modify how the iteration is done, and it uses the +// filter function to determine which actions to include in the result. +// It returns the list of selected actions, the last index that was read from, +// and the total number of actions that matched the filter function (iff +// cfg.CountAll is set). +func paginateActions(cfg *ListActionsQuery, c kvdb.RCursor, + readAction func(k, v []byte) (*Action, error), + filterFn ListActionsFilterFn) ([]*Action, uint64, uint64, error) { + + if cfg == nil { + cfg = &ListActionsQuery{} + } + + if filterFn == nil { + filterFn = func(a *Action, reversed bool) (bool, bool) { + return true, true + } + } + + p := actionPaginator{ + cfg: cfg, + cursor: c, + readAction: readAction, + filterFn: filterFn, + } + + if cfg.CountAll { + return p.queryCountAll() + } + + actions, lastIndex, err := p.query() + + return actions, lastIndex, 0, err +} + +// keyValueForIndex seeks our cursor to a given index and returns the key and +// value at that position. +func (p *actionPaginator) keyValueForIndex(index uint64) ([]byte, []byte) { + var keyIndex [8]byte + byteOrder.PutUint64(keyIndex[:], index) + return p.cursor.Seek(keyIndex[:]) +} + +// lastIndex returns the last value in our index, if our index is empty it +// returns 0. +func (p *actionPaginator) lastIndex() uint64 { + keyIndex, _ := p.cursor.Last() + if keyIndex == nil { + return 0 + } + + return byteOrder.Uint64(keyIndex) +} + +// nextKey is a helper closure to determine what key we should use next when +// we are iterating, depending on whether we are iterating forwards or in +// reverse. +func (p *actionPaginator) nextKey() ([]byte, []byte) { + if p.cfg.Reversed { + return p.cursor.Prev() + } + return p.cursor.Next() +} + +// cursorStart gets the index key and value for the first item we are looking +// up, taking into account that we may be paginating in reverse. The index +// offset provided is *excusive* so we will start with the item after the offset +// for forwards queries, and the item before the index for backwards queries. +func (p *actionPaginator) cursorStart() ([]byte, []byte) { + indexKey, indexValue := p.keyValueForIndex(p.cfg.IndexOffset + 1) + + // If the query is specifying reverse iteration, then we must + // handle a few offset cases. + if p.cfg.Reversed { + switch { + // This indicates the default case, where no offset was + // specified. In that case we just start from the last + // entry. + case p.cfg.IndexOffset == 0: + indexKey, indexValue = p.cursor.Last() + + // This indicates the offset being set to the very + // first entry. Since there are no entries before + // this offset, and the direction is reversed, we can + // return without adding any invoices to the response. + case p.cfg.IndexOffset == 1: + return nil, nil + + // If we have been given an index offset that is beyond our last + // index value, we just return the last indexed value in our set + // since we are querying in reverse. We do not cover the case + // where our index offset equals our last index value, because + // index offset is exclusive, so we would want to start at the + // value before our last index. + case p.cfg.IndexOffset > p.lastIndex(): + return p.cursor.Last() + + // Otherwise we have an index offset which is within our set of + // indexed keys, and we want to start at the item before our + // offset. We seek to our index offset, then return the element + // before it. We do this rather than p.indexOffset-1 to account + // for indexes that have gaps. + default: + p.keyValueForIndex(p.cfg.IndexOffset) + indexKey, indexValue = p.cursor.Prev() + } + } + + return indexKey, indexValue +} + +// query gets the start point for our index offset and iterates through keys +// in our index until we reach the total number of items required for the query +// or we run out of cursor values. This function takes a fetchAndAppend function +// which is responsible for looking up the entry at that index, adding the entry +// to its set of return items (if desired) and return a boolean which indicates +// whether the item was added. This is required to allow the actionPaginator to +// determine when the response has the maximum number of required items. +func (p *actionPaginator) query() ([]*Action, uint64, error) { + indexKey, indexValue := p.cursorStart() + + var ( + actions []*Action + lastIndex = uint64(1) + ) + for ; indexKey != nil; indexKey, indexValue = p.nextKey() { + // If our current return payload exceeds the max number + // of invoices, then we'll exit now. + if p.cfg.MaxNum != 0 && + uint64(len(actions)) >= p.cfg.MaxNum { + + break + } + + lastIndex = binary.BigEndian.Uint64(indexKey) + + action, err := p.readAction(indexKey, indexValue) + if err != nil { + return nil, 0, err + } + + add, cont := p.filterFn(action, p.cfg.Reversed) + if !cont { + break + } + + if !add { + continue + } + + actions = append(actions, action) + } + + return actions, lastIndex, nil +} + +// queryCountAll is similar to query except that instead of only iterating over +// a limited set of actions (as defined by the cfg.IndexOffset and cfg.MaxNum), +// it will instead iterate through all actions so that it can count the total +// number of actions that match the filter function. It will however only +// return actions in the range specified by the cfg.IndexOffset and cfg.MaxNum. +// Callers should be aware that this is a much slower function than query if +// there are a large number of actions in the database. +func (p *actionPaginator) queryCountAll() ([]*Action, uint64, uint64, error) { + // Start at the very first, or very last item. + indexKey, indexValue := p.cursor.First() + if p.cfg.Reversed { + indexKey, indexValue = p.cursor.Last() + } + // Then iterate from first to last and check each action. If passes + // filter, increment total count. Only if the current index is after + // (or before (in reverse mode)) the offset do we add the action & that + // is only if the num we have collected is below MaxNum. + + var ( + actions []*Action + lastIndex = uint64(1) + beforeIndexOffset = p.cfg.IndexOffset != 0 + totalCount uint64 + ) + for ; indexKey != nil; indexKey, indexValue = p.nextKey() { + action, err := p.readAction(indexKey, indexValue) + if err != nil { + return nil, 0, 0, err + } + + add, cont := p.filterFn(action, p.cfg.Reversed) + if !cont { + break + } + + if !add { + continue + } + + totalCount++ + + if p.cfg.IndexOffset != 0 && + binary.BigEndian.Uint64(indexKey) == p.cfg.IndexOffset+1 { + + beforeIndexOffset = false + } + + // Don't add the action if we are still before the offset. + if beforeIndexOffset { + continue + } + + // If our current return payload exceeds the max number + // of invoices, then we continue without adding the action to + // our return list. + if p.cfg.MaxNum != 0 && + uint64(len(actions)) >= p.cfg.MaxNum { + + continue + } + + lastIndex = binary.BigEndian.Uint64(indexKey) + actions = append(actions, action) + } + + return actions, lastIndex, totalCount, nil +} diff --git a/firewalldb/actions.go b/firewalldb/actions.go new file mode 100644 index 000000000..0e491af86 --- /dev/null +++ b/firewalldb/actions.go @@ -0,0 +1,677 @@ +package firewalldb + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + "time" + + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/tlv" + "go.etcd.io/bbolt" +) + +const ( + typeActorName tlv.Type = 1 + typeFeature tlv.Type = 2 + typeTrigger tlv.Type = 3 + typeIntent tlv.Type = 4 + typeStructuredJsonData tlv.Type = 5 + typeRPCMethod tlv.Type = 6 + typeRPCParamsJson tlv.Type = 7 + typeAttemptedAt tlv.Type = 8 + typeState tlv.Type = 9 + typeErrorReason tlv.Type = 10 + + typeLocatorSessionID tlv.Type = 1 + typeLocatorActionID tlv.Type = 2 +) + +/* + The Actions are stored in the following structure in the KV db: + + actions-bucket -> actions -> -> -> serialised action + + -> actions-index -> -> {sessionID:action-index} +*/ + +var ( + // actionsBucketKey is the key that will be used for the main Actions + // bucket. + actionsBucketKey = []byte("actions-bucket") + + // actionsKey is the key used for the sub-bucket containing the + // session actions. + actionsKey = []byte("actions") + + // actionsIndex is the key used for the sub-bucket containing a map + // from monotonically increasing IDs to action locators. + actionsIndex = []byte("actions-index") +) + +// ActionState represents the state of an action. +type ActionState uint8 + +const ( + // ActionStateUnknown means that the action's state was never + // initialised. This should never be the case. + ActionStateUnknown ActionState = 0 + + // ActionStateInit represents that an Action has been created but that + // is still in the pending state. + ActionStateInit ActionState = 1 + + // ActionStateDone represents that an Action has been executed + // successfully. + ActionStateDone ActionState = 2 + + // ActionStateError represents that an Action did not complete + // successfully. + ActionStateError ActionState = 3 +) + +// Action represents an RPC call made through the firewall. +type Action struct { + // SessionID is the ID of the session that this action belongs to. + // Note that this is not serialized on persistence since the action is + // already stored under a bucket identified by the session ID. + SessionID session.ID + + // ActorName is the name of the entity who performed the Action. + ActorName string + + // FeatureName is the name of the feature that the Action is being + // performed by. + FeatureName string + + // Trigger is the meta info detailing what caused this action to be + // executed. + Trigger string + + // Intent is the meta info detailing what the intended outcome of this + // action will be. + Intent string + + // StructuredJsonData is extra, structured, info that the Autopilot can + // send to Litd serialised as a json string. + StructuredJsonData string + + // RPCMethod is the URI that was called. + RPCMethod string + + // RPCParams is the method parameters of the request in JSON form. + RPCParamsJson []byte + + // AttemptedAt is the time at which this action was created. + AttemptedAt time.Time + + // State represents the state of the Action. + State ActionState + + // ErrorReason is the human-readable reason for why the action failed. + // It will only be set if State is ActionStateError. + ErrorReason string +} + +// AddAction serialises and adds an Action to the DB under the given sessionID. +func (db *DB) AddAction(sessionID session.ID, action *Action) (uint64, error) { + var buf bytes.Buffer + if err := SerializeAction(&buf, action); err != nil { + return 0, err + } + + var id uint64 + err := db.DB.Update(func(tx *bbolt.Tx) error { + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + sessBucket, err := actionsBucket.CreateBucketIfNotExists( + sessionID[:], + ) + if err != nil { + return err + } + + nextActionIndex, err := sessBucket.NextSequence() + if err != nil { + return err + } + id = nextActionIndex + + var actionIndex [8]byte + byteOrder.PutUint64(actionIndex[:], nextActionIndex) + err = sessBucket.Put(actionIndex[:], buf.Bytes()) + if err != nil { + return err + } + + actionsIndexBucket := mainActionsBucket.Bucket(actionsIndex) + if actionsIndexBucket == nil { + return ErrNoSuchKeyFound + } + + nextSeq, err := actionsIndexBucket.NextSequence() + if err != nil { + return err + } + + locator := ActionLocator{ + SessionID: sessionID, + ActionID: nextActionIndex, + } + + var buf bytes.Buffer + err = serializeActionLocator(&buf, &locator) + if err != nil { + return err + } + + var seqNoBytes [8]byte + byteOrder.PutUint64(seqNoBytes[:], nextSeq) + return actionsIndexBucket.Put(seqNoBytes[:], buf.Bytes()) + }) + if err != nil { + return 0, err + } + + return id, nil +} + +func putAction(tx *bbolt.Tx, al *ActionLocator, a *Action) error { + var buf bytes.Buffer + if err := SerializeAction(&buf, a); err != nil { + return err + } + + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + sessBucket := actionsBucket.Bucket(al.SessionID[:]) + if sessBucket == nil { + return fmt.Errorf("session bucket for session ID %x does not "+ + "exist", al.SessionID) + } + + var id [8]byte + binary.BigEndian.PutUint64(id[:], al.ActionID) + + return sessBucket.Put(id[:], buf.Bytes()) +} + +func getAction(actionsBkt *bbolt.Bucket, al *ActionLocator) (*Action, error) { + sessBucket := actionsBkt.Bucket(al.SessionID[:]) + if sessBucket == nil { + return nil, fmt.Errorf("session bucket for session ID "+ + "%x does not exist", al.SessionID) + } + + var id [8]byte + binary.BigEndian.PutUint64(id[:], al.ActionID) + + actionBytes := sessBucket.Get(id[:]) + return DeserializeAction(bytes.NewReader(actionBytes), al.SessionID) +} + +// SetActionState finds the action specified by the ActionLocator and sets its +// state to the given state. +func (db *DB) SetActionState(al *ActionLocator, state ActionState, + errorReason string) error { + + if errorReason != "" && state != ActionStateError { + return fmt.Errorf("error reason should only be set for " + + "ActionStateError") + } + + return db.DB.Update(func(tx *bbolt.Tx) error { + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + action, err := getAction(actionsBucket, al) + if err != nil { + return err + } + + action.State = state + action.ErrorReason = errorReason + + return putAction(tx, al, action) + }) +} + +// ListActionsQuery can be used to tweak the query to ListActions and +// ListSessionActions. +type ListActionsQuery struct { + // IndexOffset is index of the action to inspect. + IndexOffset uint64 + + // MaxNum is the maximum number of actions to return. If it is set to 0, + // then no maximum is enforced. + MaxNum uint64 + + // Reversed indicates whether the actions should be returned in reverse + // order. + Reversed bool + + // CountAll should be set to true if the total number of actions that + // satisfy the query should be counted and returned. Note that this will + // make the query slower. + CountAll bool +} + +// ListActionsFilterFn defines a function that can be used to determine if an +// action should be included in a set of results or not. The reversed parameter +// indicates if the actions are being traversed in reverse order or not. +// The first return boolean indicates if the action should be included or not +// and the second one indicates if the iteration should be stopped or not. +type ListActionsFilterFn func(a *Action, reversed bool) (bool, bool) + +// ListActions returns a list of Actions that pass the filterFn requirements. +// The indexOffset and maxNum params can be used to control the number of +// actions returned. The return values are the list of actions, the last index +// and the total count (iff query.CountTotal is set). +func (db *DB) ListActions(filterFn ListActionsFilterFn, + query *ListActionsQuery) ([]*Action, uint64, uint64, error) { + + var ( + actions []*Action + totalCount uint64 + lastIndex uint64 + ) + err := db.View(func(tx *bbolt.Tx) error { + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + actionsIndexBucket := mainActionsBucket.Bucket(actionsIndex) + if actionsIndexBucket == nil { + return ErrNoSuchKeyFound + } + + readAction := func(index, locatorBytes []byte) (*Action, + error) { + + locator, err := deserializeActionLocator( + bytes.NewReader(locatorBytes), + ) + if err != nil { + return nil, err + } + + return getAction(actionsBucket, locator) + } + + actions, lastIndex, totalCount, err = paginateActions( + query, actionsIndexBucket.Cursor(), readAction, + filterFn, + ) + return err + }) + if err != nil { + return nil, 0, 0, err + } + + return actions, lastIndex, totalCount, nil +} + +// ListSessionActions returns a list of the given session's Actions that pass +// the filterFn requirements. +func (db *DB) ListSessionActions(sessionID session.ID, + filterFn ListActionsFilterFn, query *ListActionsQuery) ([]*Action, + uint64, uint64, error) { + + var ( + actions []*Action + totalCount uint64 + lastIndex uint64 + ) + err := db.View(func(tx *bbolt.Tx) error { + mainActionsBucket, err := getBucket(tx, actionsBucketKey) + if err != nil { + return err + } + + actionsBucket := mainActionsBucket.Bucket(actionsKey) + if actionsBucket == nil { + return ErrNoSuchKeyFound + } + + sessionsBucket := actionsBucket.Bucket(sessionID[:]) + if sessionsBucket == nil { + return nil + } + + readAction := func(_, v []byte) (*Action, error) { + return DeserializeAction(bytes.NewReader(v), sessionID) + } + + actions, lastIndex, totalCount, err = paginateActions( + query, sessionsBucket.Cursor(), readAction, filterFn, + ) + + return err + }) + if err != nil { + return nil, 0, 0, err + } + + return actions, lastIndex, totalCount, nil +} + +// SerializeAction binary serializes the given action to the writer using the +// tlv format. +func SerializeAction(w io.Writer, action *Action) error { + if action == nil { + return fmt.Errorf("action cannot be nil") + } + + var ( + actor = []byte(action.ActorName) + feature = []byte(action.FeatureName) + trigger = []byte(action.Trigger) + intent = []byte(action.Intent) + data = []byte(action.StructuredJsonData) + rpcMethod = []byte(action.RPCMethod) + params = action.RPCParamsJson + attemptedAt = uint64(action.AttemptedAt.Unix()) + state = uint8(action.State) + errorReason = []byte(action.ErrorReason) + ) + + tlvRecords := []tlv.Record{ + tlv.MakePrimitiveRecord(typeActorName, &actor), + tlv.MakePrimitiveRecord(typeFeature, &feature), + tlv.MakePrimitiveRecord(typeTrigger, &trigger), + tlv.MakePrimitiveRecord(typeIntent, &intent), + tlv.MakePrimitiveRecord(typeStructuredJsonData, &data), + tlv.MakePrimitiveRecord(typeRPCMethod, &rpcMethod), + tlv.MakePrimitiveRecord(typeRPCParamsJson, ¶ms), + tlv.MakePrimitiveRecord(typeAttemptedAt, &attemptedAt), + tlv.MakePrimitiveRecord(typeState, &state), + tlv.MakePrimitiveRecord(typeErrorReason, &errorReason), + } + + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// DeserializeAction deserializes an action from the given reader, expecting +// the data to be encoded in the tlv format. +func DeserializeAction(r io.Reader, sessionID session.ID) (*Action, error) { + var ( + action = Action{} + actor, featureName []byte + trigger, intent, data []byte + rpcMethod, params []byte + attemptedAt uint64 + state uint8 + errorReason []byte + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(typeActorName, &actor), + tlv.MakePrimitiveRecord(typeFeature, &featureName), + tlv.MakePrimitiveRecord(typeTrigger, &trigger), + tlv.MakePrimitiveRecord(typeIntent, &intent), + tlv.MakePrimitiveRecord(typeStructuredJsonData, &data), + tlv.MakePrimitiveRecord(typeRPCMethod, &rpcMethod), + tlv.MakePrimitiveRecord(typeRPCParamsJson, ¶ms), + tlv.MakePrimitiveRecord(typeAttemptedAt, &attemptedAt), + tlv.MakePrimitiveRecord(typeState, &state), + tlv.MakePrimitiveRecord(typeErrorReason, &errorReason), + ) + if err != nil { + return nil, err + } + + _, err = tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return nil, err + } + + action.SessionID = sessionID + action.ActorName = string(actor) + action.FeatureName = string(featureName) + action.Trigger = string(trigger) + action.Intent = string(intent) + action.StructuredJsonData = string(data) + action.RPCMethod = string(rpcMethod) + action.RPCParamsJson = params + action.AttemptedAt = time.Unix(int64(attemptedAt), 0) + action.State = ActionState(state) + action.ErrorReason = string(errorReason) + + return &action, nil +} + +// ActionsWriteDB is an abstraction over the Actions DB that will allow a +// caller to add new actions as well as change the values of an existing action. +type ActionsWriteDB interface { + AddAction(sessionID session.ID, action *Action) (uint64, error) + SetActionState(al *ActionLocator, state ActionState, + errReason string) error +} + +// RuleAction represents a method call that was performed at a certain time at +// a certain time. +type RuleAction struct { + // Method is the URI of the rpc method that was called. + Method string + + // PerformedAt is the time at which the action was attempted. + PerformedAt time.Time +} + +// ActionsDB represents a DB backend that contains Action entries that can +// be queried. It allows us to abstract away the details of the data storage +// method. +type ActionsDB interface { + // ListActions returns a list of past Action items. + ListActions(ctx context.Context) ([]*RuleAction, error) +} + +// ActionsReadDB is an abstraction gives a caller access to either a session +// specific or feature specific rules.ActionDB +type ActionsReadDB interface { + SessionActionsDB() ActionsDB + FeatureActionsDB() ActionsDB +} + +// ActionReadDBGetter represents a function that can be used to construct +// an ActionsReadDB. +type ActionReadDBGetter interface { + GetActionsReadDB(sessionID session.ID, featureName string) ActionsReadDB +} + +// GetActionsReadDB is a method on DB that constructs an ActionsReadDB. +func (db *DB) GetActionsReadDB(sessionID session.ID, + featureName string) ActionsReadDB { + + return &allActionsReadDB{ + db: db, + sessionID: sessionID, + featureName: featureName, + } +} + +// allActionsReadDb is an implementation of the ActionsReadDB. +type allActionsReadDB struct { + db *DB + sessionID session.ID + featureName string +} + +var _ ActionsReadDB = (*allActionsReadDB)(nil) + +// SessionActionsDB returns a rules.ActionsDB that will give the caller access +// to all of a sessions Actions. +func (a *allActionsReadDB) SessionActionsDB() ActionsDB { + return &sessionActionsReadDB{a} +} + +// FeatureActionsDB returns an rules.ActionsDB that will give the caller access +// to only a specific features Actions in a specific session. +func (a *allActionsReadDB) FeatureActionsDB() ActionsDB { + return &featureActionsReadDB{a} +} + +// sessionActionReadDB is an implementation of the rules.ActionsDB that will +// provide read access to all the Actions of a particular session. +type sessionActionsReadDB struct { + *allActionsReadDB +} + +var _ ActionsDB = (*sessionActionsReadDB)(nil) + +// ListActions will return all the Actions for a particular session. +func (s *sessionActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, + error) { + + sessionActions, _, _, err := s.db.ListSessionActions( + s.sessionID, func(a *Action, _ bool) (bool, bool) { + return a.State == ActionStateDone, true + }, nil, + ) + if err != nil { + return nil, err + } + + actions := make([]*RuleAction, len(sessionActions)) + for i, action := range sessionActions { + actions[i] = actionToRulesAction(action) + } + + return actions, nil +} + +// featureActionReadDB is an implementation of the rules.ActionsDB that will +// provide read access to all the Actions of a feature within a particular +// session. +type featureActionsReadDB struct { + *allActionsReadDB +} + +var _ ActionsDB = (*featureActionsReadDB)(nil) + +// ListActions will return all the Actions for a particular session that were +// executed by a particular feature. +func (a *featureActionsReadDB) ListActions(_ context.Context) ([]*RuleAction, + error) { + + featureActions, _, _, err := a.db.ListSessionActions( + a.sessionID, func(action *Action, _ bool) (bool, bool) { + return action.State == ActionStateDone && + action.FeatureName == a.featureName, true + }, nil, + ) + if err != nil { + return nil, err + } + + actions := make([]*RuleAction, len(featureActions)) + for i, action := range featureActions { + actions[i] = actionToRulesAction(action) + } + + return actions, nil +} + +func actionToRulesAction(a *Action) *RuleAction { + return &RuleAction{ + Method: a.RPCMethod, + PerformedAt: a.AttemptedAt, + } +} + +// ActionLocator helps us find an action in the database. +type ActionLocator struct { + SessionID session.ID + ActionID uint64 +} + +// serializeActionLocator binary serializes the given ActionLocator to the +// writer using the tlv format. +func serializeActionLocator(w io.Writer, al *ActionLocator) error { + if al == nil { + return fmt.Errorf("action locator cannot be nil") + } + + var ( + sessionID = al.SessionID[:] + actionID = al.ActionID + ) + + tlvRecords := []tlv.Record{ + tlv.MakePrimitiveRecord(typeLocatorSessionID, &sessionID), + tlv.MakePrimitiveRecord(typeLocatorActionID, &actionID), + } + + tlvStream, err := tlv.NewStream(tlvRecords...) + if err != nil { + return err + } + + return tlvStream.Encode(w) +} + +// deserializeActionLocator deserializes an ActionLocator from the given reader, +// expecting the data to be encoded in the tlv format. +func deserializeActionLocator(r io.Reader) (*ActionLocator, error) { + var ( + sessionID []byte + actionID uint64 + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(typeLocatorSessionID, &sessionID), + tlv.MakePrimitiveRecord(typeLocatorActionID, &actionID), + ) + if err != nil { + return nil, err + } + + _, err = tlvStream.DecodeWithParsedTypes(r) + if err != nil { + return nil, err + } + + id, err := session.IDFromBytes(sessionID) + if err != nil { + return nil, err + } + + return &ActionLocator{ + SessionID: id, + ActionID: actionID, + }, nil +} diff --git a/firewalldb/actions_test.go b/firewalldb/actions_test.go new file mode 100644 index 000000000..e0502903d --- /dev/null +++ b/firewalldb/actions_test.go @@ -0,0 +1,337 @@ +package firewalldb + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestActionStorage tests that the ActionsDB CRUD logic. +func TestActionStorage(t *testing.T) { + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + sessionID1 := [4]byte{1, 1, 1, 1} + action1 := &Action{ + SessionID: sessionID1, + ActorName: "Autopilot", + FeatureName: "auto-fees", + Trigger: "fee too low", + Intent: "increase fee", + StructuredJsonData: "{\"something\":\"nothing\"}", + RPCMethod: "UpdateChanPolicy", + RPCParamsJson: []byte("new fee"), + AttemptedAt: time.Unix(32100, 0), + State: ActionStateDone, + } + + sessionID2 := [4]byte{2, 2, 2, 2} + action2 := &Action{ + SessionID: sessionID2, + ActorName: "Autopilot", + FeatureName: "rebalancer", + Trigger: "channels not balanced", + Intent: "balance", + RPCMethod: "SendToRoute", + RPCParamsJson: []byte("hops, amount"), + AttemptedAt: time.Unix(12300, 0), + State: ActionStateInit, + } + + actionsStateFilterFn := func(state ActionState) ListActionsFilterFn { + return func(a *Action, _ bool) (bool, bool) { + return a.State == state, true + } + } + + actions, _, _, err := db.ListSessionActions( + sessionID1, actionsStateFilterFn(ActionStateDone), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 0) + + actions, _, _, err = db.ListSessionActions( + sessionID2, actionsStateFilterFn(ActionStateDone), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 0) + + id, err := db.AddAction(sessionID1, action1) + require.NoError(t, err) + require.Equal(t, uint64(1), id) + + id, err = db.AddAction(sessionID2, action2) + require.NoError(t, err) + require.Equal(t, uint64(1), id) + + actions, _, _, err = db.ListSessionActions( + sessionID1, actionsStateFilterFn(ActionStateDone), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 1) + require.Equal(t, action1, actions[0]) + + actions, _, _, err = db.ListSessionActions( + sessionID2, actionsStateFilterFn(ActionStateDone), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 0) + + err = db.SetActionState( + &ActionLocator{ + SessionID: sessionID2, + ActionID: uint64(1), + }, ActionStateDone, "", + ) + require.NoError(t, err) + + actions, _, _, err = db.ListSessionActions( + sessionID2, actionsStateFilterFn(ActionStateDone), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 1) + action2.State = ActionStateDone + require.Equal(t, action2, actions[0]) + + id, err = db.AddAction(sessionID1, action1) + require.NoError(t, err) + require.Equal(t, uint64(2), id) + + // Check that providing no session id and no filter function returns + // all the actions. + actions, _, _, err = db.ListActions(nil, &ListActionsQuery{ + IndexOffset: 0, + MaxNum: 100, + Reversed: false, + }) + require.NoError(t, err) + require.Len(t, actions, 3) + + // Try set an error reason for a non Errored state. + err = db.SetActionState( + &ActionLocator{ + SessionID: sessionID2, + ActionID: uint64(1), + }, ActionStateDone, "hello", + ) + require.Error(t, err) + + // Now try move the action to errored with a reason. + err = db.SetActionState( + &ActionLocator{ + SessionID: sessionID2, + ActionID: uint64(1), + }, ActionStateError, "fail whale", + ) + require.NoError(t, err) + + actions, _, _, err = db.ListSessionActions( + sessionID2, actionsStateFilterFn(ActionStateError), nil, + ) + require.NoError(t, err) + require.Len(t, actions, 1) + action2.State = ActionStateError + action2.ErrorReason = "fail whale" + require.Equal(t, action2, actions[0]) +} + +// TestListActions tests some ListAction options. +// TODO(elle): cover more test cases here. +func TestListActions(t *testing.T) { + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + sessionID1 := [4]byte{1, 1, 1, 1} + sessionID2 := [4]byte{2, 2, 2, 2} + + actionIds := 0 + addAction := func(sessionID [4]byte) { + actionIds++ + action := &Action{ + SessionID: sessionID, + ActorName: "Autopilot", + FeatureName: fmt.Sprintf("%d", actionIds), + Trigger: "fee too low", + Intent: "increase fee", + StructuredJsonData: "{\"something\":\"nothing\"}", + RPCMethod: "UpdateChanPolicy", + RPCParamsJson: []byte("new fee"), + AttemptedAt: time.Unix(32100, 0), + State: ActionStateDone, + } + + _, err := db.AddAction(sessionID, action) + require.NoError(t, err) + } + + type action struct { + sessionID [4]byte + actionID string + } + + assertActions := func(dbActions []*Action, al []*action) { + require.Len(t, dbActions, len(al)) + for i, a := range al { + require.EqualValues( + t, a.sessionID, dbActions[i].SessionID, + ) + require.Equal(t, a.actionID, dbActions[i].FeatureName) + } + } + + addAction(sessionID1) + addAction(sessionID1) + addAction(sessionID1) + addAction(sessionID1) + addAction(sessionID2) + + actions, lastIndex, totalCount, err := db.ListActions(nil, nil) + require.NoError(t, err) + require.Len(t, actions, 5) + require.EqualValues(t, 5, lastIndex) + require.EqualValues(t, 0, totalCount) + assertActions(actions, []*action{ + {sessionID1, "1"}, + {sessionID1, "2"}, + {sessionID1, "3"}, + {sessionID1, "4"}, + {sessionID2, "5"}, + }) + + query := &ListActionsQuery{ + Reversed: true, + } + + actions, lastIndex, totalCount, err = db.ListActions(nil, query) + require.NoError(t, err) + require.Len(t, actions, 5) + require.EqualValues(t, 1, lastIndex) + require.EqualValues(t, 0, totalCount) + assertActions(actions, []*action{ + {sessionID2, "5"}, + {sessionID1, "4"}, + {sessionID1, "3"}, + {sessionID1, "2"}, + {sessionID1, "1"}, + }) + + actions, lastIndex, totalCount, err = db.ListActions( + nil, &ListActionsQuery{ + CountAll: true, + }, + ) + require.NoError(t, err) + require.Len(t, actions, 5) + require.EqualValues(t, 5, lastIndex) + require.EqualValues(t, 5, totalCount) + assertActions(actions, []*action{ + {sessionID1, "1"}, + {sessionID1, "2"}, + {sessionID1, "3"}, + {sessionID1, "4"}, + {sessionID2, "5"}, + }) + + actions, lastIndex, totalCount, err = db.ListActions( + nil, &ListActionsQuery{ + CountAll: true, + Reversed: true, + }, + ) + require.NoError(t, err) + require.Len(t, actions, 5) + require.EqualValues(t, 1, lastIndex) + require.EqualValues(t, 5, totalCount) + assertActions(actions, []*action{ + {sessionID2, "5"}, + {sessionID1, "4"}, + {sessionID1, "3"}, + {sessionID1, "2"}, + {sessionID1, "1"}, + }) + + addAction(sessionID2) + addAction(sessionID2) + addAction(sessionID1) + addAction(sessionID1) + addAction(sessionID2) + + actions, lastIndex, totalCount, err = db.ListActions(nil, nil) + require.NoError(t, err) + require.Len(t, actions, 10) + require.EqualValues(t, 10, lastIndex) + require.EqualValues(t, 0, totalCount) + assertActions(actions, []*action{ + {sessionID1, "1"}, + {sessionID1, "2"}, + {sessionID1, "3"}, + {sessionID1, "4"}, + {sessionID2, "5"}, + {sessionID2, "6"}, + {sessionID2, "7"}, + {sessionID1, "8"}, + {sessionID1, "9"}, + {sessionID2, "10"}, + }) + + actions, lastIndex, totalCount, err = db.ListActions( + nil, &ListActionsQuery{ + MaxNum: 3, + CountAll: true, + }, + ) + require.NoError(t, err) + require.Len(t, actions, 3) + require.EqualValues(t, 3, lastIndex) + require.EqualValues(t, 10, totalCount) + assertActions(actions, []*action{ + {sessionID1, "1"}, + {sessionID1, "2"}, + {sessionID1, "3"}, + }) + + actions, lastIndex, totalCount, err = db.ListActions( + nil, &ListActionsQuery{ + MaxNum: 3, + IndexOffset: 3, + }, + ) + require.NoError(t, err) + require.Len(t, actions, 3) + require.EqualValues(t, 6, lastIndex) + require.EqualValues(t, 0, totalCount) + assertActions(actions, []*action{ + {sessionID1, "4"}, + {sessionID2, "5"}, + {sessionID2, "6"}, + }) + + actions, lastIndex, totalCount, err = db.ListActions( + nil, &ListActionsQuery{ + MaxNum: 3, + IndexOffset: 3, + CountAll: true, + }, + ) + require.NoError(t, err) + require.Len(t, actions, 3) + require.EqualValues(t, 6, lastIndex) + require.EqualValues(t, 10, totalCount) + assertActions(actions, []*action{ + {sessionID1, "4"}, + {sessionID2, "5"}, + {sessionID2, "6"}, + }) +} diff --git a/firewalldb/db.go b/firewalldb/db.go index e0e7f81bb..ee601d8c2 100644 --- a/firewalldb/db.go +++ b/firewalldb/db.go @@ -30,6 +30,10 @@ var ( // byteOrder is the default byte order we'll use for serialization // within the database. byteOrder = binary.BigEndian + + // ErrNoSuchKeyFound is returned when there is no key-value pair found + // for the given key. + ErrNoSuchKeyFound = fmt.Errorf("no such key found") ) // DB is a bolt-backed persistent store. @@ -102,7 +106,20 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { } } - return nil + actionsBucket, err := tx.CreateBucketIfNotExists( + actionsBucketKey, + ) + if err != nil { + return err + } + + _, err = actionsBucket.CreateBucketIfNotExists(actionsKey) + if err != nil { + return err + } + + _, err = actionsBucket.CreateBucketIfNotExists(actionsIndex) + return err }) if err != nil { return nil, err From 0a9f5458cb7eeb97eafcfd101856d2ff56db342d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 13:53:14 +0200 Subject: [PATCH 11/47] firewalldb: add KVStore CRUD logic --- firewalldb/db.go | 13 + firewalldb/kvstores.go | 465 ++++++++++++++++++++++++++++++++++++ firewalldb/kvstores_test.go | 327 +++++++++++++++++++++++++ 3 files changed, 805 insertions(+) create mode 100644 firewalldb/kvstores.go create mode 100644 firewalldb/kvstores_test.go diff --git a/firewalldb/db.go b/firewalldb/db.go index ee601d8c2..65ca29971 100644 --- a/firewalldb/db.go +++ b/firewalldb/db.go @@ -2,6 +2,7 @@ package firewalldb import ( "encoding/binary" + "errors" "fmt" "os" "path/filepath" @@ -106,6 +107,18 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { } } + rulesBucket, err := tx.CreateBucketIfNotExists(rulesBucketKey) + if err != nil { + return err + } + + // Delete everything under the "temp" key if such a bucket + // exists. + err = rulesBucket.DeleteBucket(tempBucketKey) + if err != nil && !errors.Is(err, bbolt.ErrBucketNotFound) { + return err + } + actionsBucket, err := tx.CreateBucketIfNotExists( actionsBucketKey, ) diff --git a/firewalldb/kvstores.go b/firewalldb/kvstores.go new file mode 100644 index 000000000..caf44e9b7 --- /dev/null +++ b/firewalldb/kvstores.go @@ -0,0 +1,465 @@ +package firewalldb + +import ( + "context" + + "github.com/lightninglabs/lightning-terminal/session" + "go.etcd.io/bbolt" +) + +/* +The KVStores are stored in the following structure in the KV db. Note that +the `perm` and `temp` buckets are identical in structure. The only difference is +that the `temp` bucket is cleared on restart of the db. + +rules -> perm -> rule-name -> global -> {k:v} + -> sessions -> sessionID -> session-kv-store -> {k:v} + -> feature-kv-stores -> feature-name -> {k:v} + + -> temp -> rule-name -> global -> {k:v} + -> sessions -> sessionID -> session-kv-store -> {k:v} + -> feature-kv-stores -> feature-name -> {k:v} +*/ + +var ( + // rulesBucketKey is the key under which all things rule-kvstore + // related will fall. + rulesBucketKey = []byte("rules") + + // permBucketKey is a sub bucket under the rules bucket. Everything + // stored under this key is persisted across restarts. + permBucketKey = []byte("perm") + + // tempBucketKey is a sub bucket under the rules bucket. Everything + // stored under this key is cleared on restart of the db. + tempBucketKey = []byte("temp") + + // globalKVStoreBucketKey is a key under which a kv store is that will + // always be available to a specific rule regardless of which session + // or feature is being evaluated. + globalKVStoreBucketKey = []byte("global") + + // sessKVStoreBucketKey is the key under which a session wide kv store + // for the rule is stored. + sessKVStoreBucketKey = []byte("session-kv-store") + + // featureKVStoreBucketKey is the kye under which a kv store specific + // the session id and feature name is stored. + featureKVStoreBucketKey = []byte("feature-kv-store") +) + +// KVStores provides an Update and View method that will allow the caller to +// perform atomic read and write transactions on and of the key value stores +// offered the KVStoreTx. +type KVStores interface { + // Update opens a database read/write transaction and executes the + // function f with the transaction passed as a parameter. After f exits, + // if f did not error, the transaction is committed. Otherwise, if f did + // error, the transaction is rolled back. If the rollback fails, the + // original error returned by f is still returned. If the commit fails, + // the commit error is returned. + Update(f func(tx KVStoreTx) error) error + + // View opens a database read transaction and executes the function f + // with the transaction passed as a parameter. After f exits, the + // transaction is rolled back. If f errors, its error is returned, not a + // rollback error (if any occur). + View(f func(tx KVStoreTx) error) error +} + +// KVStoreTx represents a database transaction that can be used for both read +// and writes of the various different key value stores offered for the rule. +type KVStoreTx interface { + // Global returns a persisted global, rule-name indexed, kv store. A + // rule with a given name will have access to this store independent of + // session ID or feature. + Global() KVStore + + // Local returns a persisted local kv store for the rule. Depending on + // how the implementation is initialised, this will either be under the + // session ID namespace or the session ID _and_ feature name namespace. + Local() KVStore + + // GlobalTemp is similar to the Global store except that its contents + // is cleared upon restart of the database. + GlobalTemp() KVStore + + // LocalTemp is similar to the Local store except that its contents is + // cleared upon restart of the database. + LocalTemp() KVStore +} + +// KVStore is in interface representing a key value store. It allows us to +// abstract away the details of the data storage method. +type KVStore interface { + // Get fetches the value under the given key from the underlying kv + // store. If no value is found, nil is returned. + Get(ctx context.Context, key string) ([]byte, error) + + // Set sets the given key-value pair in the underlying kv store. + Set(ctx context.Context, key string, value []byte) error + + // Del deletes the value under the given key in the underlying kv store. + Del(ctx context.Context, key string) error +} + +// RulesDB can be used to initialise a new rules.KVStores. +type RulesDB interface { + GetKVStores(rule string, sessionID session.ID, feature string) KVStores +} + +// GetKVStores constructs a new rules.KVStores backed by a bbolt db. +func (db *DB) GetKVStores(rule string, sessionID session.ID, + feature string) KVStores { + + return &kvStores{ + DB: db, + ruleName: rule, + sessionID: sessionID, + featureName: feature, + } +} + +// kvStores implements the rules.KVStores interface. +type kvStores struct { + *DB + ruleName string + sessionID session.ID + featureName string +} + +// beginTx starts db transaction. The transaction will be a read or read-write +// transaction depending on the value of the `writable` parameter. +func (s *kvStores) beginTx(writable bool) (*kvStoreTx, error) { + boltTx, err := s.Begin(writable) + if err != nil { + return nil, err + } + return &kvStoreTx{ + kvStores: s, + boltTx: boltTx, + }, nil +} + +// Update opens a database read/write transaction and executes the function f +// with the transaction passed as a parameter. After f exits, if f did not +// error, the transaction is committed. Otherwise, if f did error, the +// transaction is rolled back. If the rollback fails, the original error +// returned by f is still returned. If the commit fails, the commit error is +// returned. +// +// NOTE: this is part of the KVStores interface. +func (s *kvStores) Update(f func(tx KVStoreTx) error) error { + tx, err := s.beginTx(true) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if tx != nil { + _ = tx.boltTx.Rollback() + } + }() + + err = f(tx) + if err != nil { + // Want to return the original error, not a rollback error if + // any occur. + _ = tx.boltTx.Rollback() + return err + } + + return tx.boltTx.Commit() +} + +// View opens a database read transaction and executes the function f with the +// transaction passed as a parameter. After f exits, the transaction is rolled +// back. If f errors, its error is returned, not a rollback error (if any +// occur). +// +// NOTE: this is part of the KVStores interface. +func (s *kvStores) View(f func(tx KVStoreTx) error) error { + tx, err := s.beginTx(false) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if tx != nil { + _ = tx.boltTx.Rollback() + } + }() + + err = f(tx) + rollbackErr := tx.boltTx.Rollback() + if err != nil { + return err + } + + if rollbackErr != nil { + return rollbackErr + } + return nil +} + +// getBucketFunc defines the signature of the bucket creation/fetching function +// required by kvStoreTx. If create is true, then all the bucket (and all +// buckets leading up to the bucket) should be created if they do not already +// exist. Otherwise, if the bucket or any leading up to it does not yet exist +// then nil is returned. +type getBucketFunc func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) + +// kvStoreTx represents an open transaction of kvStores. +// This implements the KVStoreTX interface. +type kvStoreTx struct { + boltTx *bbolt.Tx + getBucket getBucketFunc + + *kvStores +} + +// Global gives the caller access to the global kv store of the rule. +// +// NOTE: this is part of the rules.KVStoreTx interface. +func (tx *kvStoreTx) Global() KVStore { + return &kvStoreTx{ + kvStores: tx.kvStores, + boltTx: tx.boltTx, + getBucket: getGlobalRuleBucket(true, tx.ruleName), + } +} + +// Local gives the caller access to the local kv store of the rule. This will +// either be a session wide kv store or a feature specific one depending on +// how the kv store was initialised. +// +// NOTE: this is part of the KVStoreTx interface. +func (tx *kvStoreTx) Local() KVStore { + fn := getSessionRuleBucket(true, tx.ruleName, tx.sessionID) + if tx.featureName != "" { + fn = getSessionFeatureRuleBucket( + true, tx.ruleName, tx.sessionID, tx.featureName, + ) + } + + return &kvStoreTx{ + kvStores: tx.kvStores, + boltTx: tx.boltTx, + getBucket: fn, + } +} + +// GlobalTemp gives the caller access to the temporary global kv store of the +// rule. +// +// NOTE: this is part of the KVStoreTx interface. +func (tx *kvStoreTx) GlobalTemp() KVStore { + return &kvStoreTx{ + kvStores: tx.kvStores, + boltTx: tx.boltTx, + getBucket: getGlobalRuleBucket(false, tx.ruleName), + } +} + +// LocalTemp gives the caller access to the temporary local kv store of the +// rule. +// +// NOTE: this is part of the KVStoreTx interface. +func (tx *kvStoreTx) LocalTemp() KVStore { + fn := getSessionRuleBucket(true, tx.ruleName, tx.sessionID) + if tx.featureName != "" { + fn = getSessionFeatureRuleBucket( + false, tx.ruleName, tx.sessionID, tx.featureName, + ) + } + + return &kvStoreTx{ + kvStores: tx.kvStores, + boltTx: tx.boltTx, + getBucket: fn, + } +} + +// Get fetches the value under the given key from the underlying kv store. +// If no value is found, nil is returned. +// +// NOTE: this is part of the KVStore interface. +func (tx *kvStoreTx) Get(_ context.Context, key string) ([]byte, error) { + bucket, err := tx.getBucket(tx.boltTx, false) + if err != nil { + return nil, err + } + if bucket == nil { + return nil, nil + } + + return bucket.Get([]byte(key)), nil +} + +// Set sets the given key-value pair in the underlying kv store. +// +// NOTE: this is part of the KVStore interface. +func (tx *kvStoreTx) Set(_ context.Context, key string, value []byte) error { + bucket, err := tx.getBucket(tx.boltTx, true) + if err != nil { + return err + } + + return bucket.Put([]byte(key), value) +} + +// Del deletes the value under the given key in the underlying kv store. +// +// NOTE: this is part of the .KVStore interface. +func (tx *kvStoreTx) Del(_ context.Context, key string) error { + bucket, err := tx.getBucket(tx.boltTx, false) + if err != nil { + return err + } + if bucket == nil { + return nil + } + + return bucket.Delete([]byte(key)) +} + +func getMainBucket(tx *bbolt.Tx, create, perm bool) (*bbolt.Bucket, error) { + mainBucket, err := getBucket(tx, rulesBucketKey) + if err != nil { + return nil, err + } + + key := tempBucketKey + if perm { + key = permBucketKey + } + + if create { + return mainBucket.CreateBucketIfNotExists(key) + } + + return mainBucket.Bucket(key), nil +} + +// getRuleBucket returns a function that can be used to access the bucket for +// a given rule name. The `perm` param determines if the temporary or permanent +// store is used. +func getRuleBucket(perm bool, ruleName string) getBucketFunc { + return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { + mainBucket, err := getMainBucket(tx, create, perm) + if err != nil { + return nil, err + } + + if create { + return mainBucket.CreateBucketIfNotExists( + []byte(ruleName), + ) + } else if mainBucket == nil { + return nil, nil + } + + return mainBucket.Bucket([]byte(ruleName)), nil + } +} + +// getGlobalRuleBucket returns a function that can be used to access the global +// kv store of the given rule name. The `perm` param determines if the temporary +// or permanent store is used. +func getGlobalRuleBucket(perm bool, ruleName string) getBucketFunc { + return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { + ruleBucket, err := getRuleBucket(perm, ruleName)(tx, create) + if err != nil { + return nil, err + } + + if ruleBucket == nil && !create { + return nil, nil + } + + if create { + return ruleBucket.CreateBucketIfNotExists( + globalKVStoreBucketKey, + ) + } + + return ruleBucket.Bucket(globalKVStoreBucketKey), nil + } +} + +// getSessionRuleBucket returns a function that can be used to fetch the +// bucket under which a kv store for a specific rule-name and session ID is +// stored. The `perm` param determines if the temporary or permanent store is +// used. +func getSessionRuleBucket(perm bool, ruleName string, + sessionID session.ID) getBucketFunc { + + return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { + ruleBucket, err := getRuleBucket(perm, ruleName)(tx, create) + if err != nil { + return nil, err + } + + if ruleBucket == nil && !create { + return nil, nil + } + + if create { + sessBucket, err := ruleBucket.CreateBucketIfNotExists( + sessKVStoreBucketKey, + ) + if err != nil { + return nil, err + } + + return sessBucket.CreateBucketIfNotExists(sessionID[:]) + } + + sessBucket := ruleBucket.Bucket(sessKVStoreBucketKey) + if sessBucket == nil { + return nil, nil + } + return sessBucket.Bucket(sessionID[:]), nil + } +} + +// getSessionFeatureRuleBucket returns a function that can be used to fetch the +// bucket under which a kv store for a specific rule-name, session ID and +// feature name is stored. The `perm` param determines if the temporary or +// permanent store is used. +func getSessionFeatureRuleBucket(perm bool, ruleName string, + sessionID session.ID, featureName string) getBucketFunc { + + return func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error) { + sessBucket, err := getSessionRuleBucket( + perm, ruleName, sessionID)(tx, create) + if err != nil { + return nil, err + } + + if sessBucket == nil && !create { + return nil, nil + } + + if create { + featureBucket, err := sessBucket.CreateBucketIfNotExists( + featureKVStoreBucketKey, + ) + if err != nil { + return nil, err + } + + return featureBucket.CreateBucketIfNotExists( + []byte(featureName), + ) + } + + featureBucket := sessBucket.Bucket(featureKVStoreBucketKey) + if featureBucket == nil { + return nil, nil + } + return featureBucket.Bucket([]byte(featureName)), nil + } +} diff --git a/firewalldb/kvstores_test.go b/firewalldb/kvstores_test.go new file mode 100644 index 000000000..dc5076439 --- /dev/null +++ b/firewalldb/kvstores_test.go @@ -0,0 +1,327 @@ +package firewalldb + +import ( + "bytes" + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestKVStoreTxs tests that the `Update` and `View` functions correctly provide +// atomic access to the db. If anything fails in the middle of an `Update` +// function, then all the changes prior should be rolled back. +func TestKVStoreTxs(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + store := db.GetKVStores("AutoFees", [4]byte{1, 1, 1, 1}, "auto-fees") + + // Test that if an action fails midway through the transaction, then + // it is rolled back. + err = store.Update(func(tx KVStoreTx) error { + err := tx.Global().Set(ctx, "test", []byte{1}) + if err != nil { + return err + } + + b, err := tx.Global().Get(ctx, "test") + if err != nil { + return err + } + require.True(t, bytes.Equal(b, []byte{1})) + + // Now return an error. + return fmt.Errorf("random error") + }) + require.Error(t, err) + + var v []byte + err = store.View(func(tx KVStoreTx) error { + b, err := tx.Global().Get(ctx, "test") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.Nil(t, v) +} + +// TestTempAndPermStores tests that the kv stores stored under the `temp` bucket +// are properly deleted and re-initialised upon restart but that anything under +// the `perm` bucket is retained. +func TestTempAndPermStores(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + store := db.GetKVStores("test-rule", [4]byte{1, 1, 1, 1}, "auto-fees") + + err = store.Update(func(tx KVStoreTx) error { + // Set an item in the temp store. + err := tx.LocalTemp().Set(ctx, "test", []byte{4, 3, 2}) + if err != nil { + return err + } + + // Set an item in the perm store. + return tx.Local().Set(ctx, "test", []byte{6, 5, 4}) + }) + require.NoError(t, err) + + // Make sure that the newly added items are properly reflected _before_ + // restart. + var ( + v1 []byte + v2 []byte + ) + err = store.View(func(tx KVStoreTx) error { + b, err := tx.LocalTemp().Get(ctx, "test") + if err != nil { + return err + } + v1 = b + + b, err = tx.Local().Get(ctx, "test") + if err != nil { + return err + } + v2 = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v1, []byte{4, 3, 2})) + require.True(t, bytes.Equal(v2, []byte{6, 5, 4})) + + // Close the db. + require.NoError(t, db.Close()) + + // Restart it. + db, err = NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + _ = os.RemoveAll(tmpDir) + }) + store = db.GetKVStores("test-rule", [4]byte{1, 1, 1, 1}, "auto-fees") + + // The temp store should no longer have the stored value but the perm + // store should . + err = store.View(func(tx KVStoreTx) error { + b, err := tx.LocalTemp().Get(ctx, "test") + if err != nil { + return err + } + v1 = b + + b, err = tx.Local().Get(ctx, "test") + if err != nil { + return err + } + v2 = b + return nil + }) + require.NoError(t, err) + require.Nil(t, v1) + require.True(t, bytes.Equal(v2, []byte{6, 5, 4})) +} + +// TestKVStoreNameSpaces tests that the various name spaces are used correctly. +func TestKVStoreNameSpaces(t *testing.T) { + ctx := context.Background() + tmpDir := t.TempDir() + + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + var ( + sessionID1 = [4]byte{1, 1, 1, 1} + sessionID2 = [4]byte{2, 2, 2, 2} + ) + + // Two DBs for same session but different features. + rulesDB1 := db.GetKVStores("test-rule", sessionID1, "auto-fees") + rulesDB2 := db.GetKVStores("test-rule", sessionID1, "re-balance") + + // The third DB is for the same rule but a different session. It is + // for the same feature as db 2. + rulesDB3 := db.GetKVStores("test-rule", sessionID2, "re-balance") + + // Test that the three ruleDBs share the same global space. + err = rulesDB1.Update(func(tx KVStoreTx) error { + return tx.Global().Set( + ctx, "test-global", []byte("global thing!"), + ) + }) + require.NoError(t, err) + + err = rulesDB2.Update(func(tx KVStoreTx) error { + return tx.Global().Set( + ctx, "test-global", []byte("different global thing!"), + ) + }) + require.NoError(t, err) + + err = rulesDB3.Update(func(tx KVStoreTx) error { + return tx.Global().Set( + ctx, "test-global", []byte("yet another global thing"), + ) + }) + require.NoError(t, err) + + var v []byte + err = rulesDB1.View(func(tx KVStoreTx) error { + b, err := tx.Global().Get(ctx, "test-global") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("yet another global thing"))) + + err = rulesDB2.View(func(tx KVStoreTx) error { + b, err := tx.Global().Get(ctx, "test-global") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("yet another global thing"))) + + err = rulesDB3.View(func(tx KVStoreTx) error { + b, err := tx.Global().Get(ctx, "test-global") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("yet another global thing"))) + + // Test that the feature space is not shared by any of the dbs. + err = rulesDB1.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "count", []byte("1")) + }) + require.NoError(t, err) + + err = rulesDB2.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "count", []byte("2")) + }) + require.NoError(t, err) + + err = rulesDB3.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "count", []byte("3")) + }) + require.NoError(t, err) + + err = rulesDB1.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "count") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("1"))) + + err = rulesDB2.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "count") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("2"))) + + err = rulesDB3.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "count") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("3"))) + + // Test that the session space is shared by the first two dbs but not + // the third. To do this, we re-init the DB's but leave the feature + // names out. This way, we will access the session storage. + rulesDB1 = db.GetKVStores("test-rule", sessionID1, "") + rulesDB2 = db.GetKVStores("test-rule", sessionID1, "") + rulesDB3 = db.GetKVStores("test-rule", sessionID2, "") + + err = rulesDB1.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "test", []byte("thing 1")) + }) + require.NoError(t, err) + + err = rulesDB2.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "test", []byte("thing 2")) + }) + require.NoError(t, err) + + err = rulesDB3.Update(func(tx KVStoreTx) error { + return tx.Local().Set(ctx, "test", []byte("thing 3")) + }) + require.NoError(t, err) + + err = rulesDB1.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "test") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("thing 2"))) + + err = rulesDB2.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "test") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("thing 2"))) + + err = rulesDB3.View(func(tx KVStoreTx) error { + b, err := tx.Local().Get(ctx, "test") + if err != nil { + return err + } + v = b + return nil + }) + require.NoError(t, err) + require.True(t, bytes.Equal(v, []byte("thing 3"))) +} From 95fc0c201114eb247fbbdd5919b4c4d0ab5bb9c4 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 13:54:24 +0200 Subject: [PATCH 12/47] firewalldb: db logic for the privacy mapper --- firewalldb/db.go | 5 + firewalldb/privacy_mapper.go | 462 ++++++++++++++++++++++++++++++ firewalldb/privacy_mapper_test.go | 103 +++++++ 3 files changed, 570 insertions(+) create mode 100644 firewalldb/privacy_mapper.go create mode 100644 firewalldb/privacy_mapper_test.go diff --git a/firewalldb/db.go b/firewalldb/db.go index 65ca29971..7c862d5a4 100644 --- a/firewalldb/db.go +++ b/firewalldb/db.go @@ -132,6 +132,11 @@ func initDB(filepath string, firstInit bool) (*bbolt.DB, error) { } _, err = actionsBucket.CreateBucketIfNotExists(actionsIndex) + if err != nil { + return err + } + + _, err = tx.CreateBucketIfNotExists(privacyBucketKey) return err }) if err != nil { diff --git a/firewalldb/privacy_mapper.go b/firewalldb/privacy_mapper.go new file mode 100644 index 000000000..7d96060fc --- /dev/null +++ b/firewalldb/privacy_mapper.go @@ -0,0 +1,462 @@ +package firewalldb + +import ( + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/lightninglabs/lightning-terminal/session" + "go.etcd.io/bbolt" +) + +/* + The PrivacyMapper data is stored in the following structure in the db: + + privacy -> session id -> real-to-pseudo -> {k:v} + -> pseudo-to-real -> {k:v} +*/ + +const ( + txidStringLen = 64 +) + +var ( + privacyBucketKey = []byte("privacy") + realToPseudoKey = []byte("real-to-pseudo") + pseudoToRealKey = []byte("pseudo-to-real") + + pseudoStrAlphabet = []rune("abcdef0123456789") + pseudoStrAlphabetLen = len(pseudoStrAlphabet) +) + +// NewPrivacyMapDB is a function type that takes a session ID and uses it to +// construct a new PrivacyMapDB. +type NewPrivacyMapDB func(sessionID session.ID) PrivacyMapDB + +// PrivacyDB constructs a PrivacyMapDB that will be indexed under the given +// sessionID key. +func (db *DB) PrivacyDB(sessionID session.ID) PrivacyMapDB { + return &privacyMapDB{ + DB: db, + sessionID: sessionID, + } +} + +// PrivacyMapDB provides an Update and View method that will allow the caller +// to perform atomic read and write transactions defined by PrivacyMapTx on the +// underlying DB. +type PrivacyMapDB interface { + // Update opens a database read/write transaction and executes the + // function f with the transaction passed as a parameter. After f exits, + // if f did not error, the transaction is committed. Otherwise, if f did + // error, the transaction is rolled back. If the rollback fails, the + // original error returned by f is still returned. If the commit fails, + // the commit error is returned. + Update(f func(tx PrivacyMapTx) error) error + + // View opens a database read transaction and executes the function f + // with the transaction passed as a parameter. After f exits, the + // transaction is rolled back. If f errors, its error is returned, not a + // rollback error (if any occur). + View(f func(tx PrivacyMapTx) error) error +} + +// PrivacyMapTx represents a db that can be used to create, store and fetch +// real-pseudo pairs. +type PrivacyMapTx interface { + // NewPair persists a new real-pseudo pair. + NewPair(real, pseudo string) error + + // PseudoToReal returns the real value associated with the given pseudo + // value. If no such pair is found, then ErrNoSuchKeyFound is returned. + PseudoToReal(pseudo string) (string, error) + + // RealToPseudo returns the pseudo value associated with the given real + // value. If no such pair is found, then ErrNoSuchKeyFound is returned. + RealToPseudo(real string) (string, error) +} + +// privacyMapDB is an implementation of PrivacyMapDB. +type privacyMapDB struct { + *DB + sessionID session.ID +} + +// beginTx starts db transaction. The transaction will be a read or read-write +// transaction depending on the value of the `writable` parameter. +func (p *privacyMapDB) beginTx(writable bool) (*privacyMapTx, error) { + boltTx, err := p.Begin(writable) + if err != nil { + return nil, err + } + return &privacyMapTx{ + privacyMapDB: p, + boltTx: boltTx, + }, nil +} + +// Update opens a database read/write transaction and executes the function f +// with the transaction passed as a parameter. After f exits, if f did not +// error, the transaction is committed. Otherwise, if f did error, the +// transaction is rolled back. If the rollback fails, the original error +// returned by f is still returned. If the commit fails, the commit error is +// returned. +// +// NOTE: this is part of the PrivacyMapDB interface. +func (p *privacyMapDB) Update(f func(tx PrivacyMapTx) error) error { + tx, err := p.beginTx(true) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if tx != nil { + _ = tx.boltTx.Rollback() + } + }() + + err = f(tx) + if err != nil { + // Want to return the original error, not a rollback error if + // any occur. + _ = tx.boltTx.Rollback() + return err + } + + return tx.boltTx.Commit() +} + +// View opens a database read transaction and executes the function f with the +// transaction passed as a parameter. After f exits, the transaction is rolled +// back. If f errors, its error is returned, not a rollback error (if any +// occur). +// +// NOTE: this is part of the PrivacyMapDB interface. +func (p *privacyMapDB) View(f func(tx PrivacyMapTx) error) error { + tx, err := p.beginTx(false) + if err != nil { + return err + } + + // Make sure the transaction rolls back in the event of a panic. + defer func() { + if tx != nil { + _ = tx.boltTx.Rollback() + } + }() + + err = f(tx) + rollbackErr := tx.boltTx.Rollback() + if err != nil { + return err + } + + if rollbackErr != nil { + return rollbackErr + } + return nil +} + +// privacyMapTx is an implementation of PrivacyMapTx. +type privacyMapTx struct { + *privacyMapDB + boltTx *bbolt.Tx +} + +// NewPair inserts a new real-pseudo pair into the db. +func (p *privacyMapTx) NewPair(real, pseudo string) error { + privacyBucket, err := getBucket(p.boltTx, privacyBucketKey) + if err != nil { + return err + } + + sessBucket, err := privacyBucket.CreateBucketIfNotExists(p.sessionID[:]) + if err != nil { + return err + } + + realToPseudoBucket, err := sessBucket.CreateBucketIfNotExists( + realToPseudoKey, + ) + if err != nil { + return err + } + + pseudoToRealBucket, err := sessBucket.CreateBucketIfNotExists( + pseudoToRealKey, + ) + if err != nil { + return err + } + + err = realToPseudoBucket.Put([]byte(real), []byte(pseudo)) + if err != nil { + return err + } + + return pseudoToRealBucket.Put([]byte(pseudo), []byte(real)) +} + +// PseudoToReal will check the db to see if the given pseudo key exists. If +// it does then the real value is returned, else an error is returned. +func (p *privacyMapTx) PseudoToReal(pseudo string) (string, error) { + privacyBucket, err := getBucket(p.boltTx, privacyBucketKey) + if err != nil { + return "", err + } + + sessBucket := privacyBucket.Bucket(p.sessionID[:]) + if sessBucket == nil { + return "", ErrNoSuchKeyFound + } + + pseudoToRealBucket := sessBucket.Bucket(pseudoToRealKey) + if pseudoToRealBucket == nil { + return "", ErrNoSuchKeyFound + } + + real := pseudoToRealBucket.Get([]byte(pseudo)) + if len(real) == 0 { + return "", ErrNoSuchKeyFound + } + + return string(real), nil +} + +// RealToPseudo will check the db to see if the given real key exists. If +// it does then the pseudo value is returned, else an error is returned. +func (p *privacyMapTx) RealToPseudo(real string) (string, error) { + privacyBucket, err := getBucket(p.boltTx, privacyBucketKey) + if err != nil { + return "", err + } + + sessBucket := privacyBucket.Bucket(p.sessionID[:]) + if sessBucket == nil { + return "", ErrNoSuchKeyFound + } + + realToPseudoBucket := sessBucket.Bucket(realToPseudoKey) + if realToPseudoBucket == nil { + return "", ErrNoSuchKeyFound + } + + pseudo := realToPseudoBucket.Get([]byte(real)) + if len(pseudo) == 0 { + return "", ErrNoSuchKeyFound + } + + return string(pseudo), nil +} + +func HideString(tx PrivacyMapTx, real string) (string, error) { + pseudo, err := tx.RealToPseudo(real) + if err != nil && err != ErrNoSuchKeyFound { + return "", err + } + if err == nil { + return pseudo, nil + } + + pseudo, err = NewPseudoStr(len(real)) + if err != nil { + return "", err + } + + if err = tx.NewPair(real, pseudo); err != nil { + return "", err + } + + return pseudo, nil +} + +func NewPseudoStr(n int) (string, error) { + var max big.Int + max.SetUint64(uint64(pseudoStrAlphabetLen)) + + b := make([]rune, n) + for i := range b { + index, err := rand.Int(rand.Reader, &max) + if err != nil { + return "", err + } + + b[i] = pseudoStrAlphabet[index.Uint64()] + } + + return string(b), nil +} + +func RevealString(tx PrivacyMapTx, pseudo string) (string, error) { + if pseudo == "" { + return pseudo, nil + } + + return tx.PseudoToReal(pseudo) +} + +func HideUint64(tx PrivacyMapTx, real uint64) (uint64, error) { + str := Uint64ToStr(real) + pseudo, err := tx.RealToPseudo(str) + if err != nil && err != ErrNoSuchKeyFound { + return 0, err + } + if err == nil { + return StrToUint64(pseudo) + } + + pseudoUint64, pseudoUint64Str := NewPseudoUint64() + if err := tx.NewPair(str, pseudoUint64Str); err != nil { + return 0, err + } + + return pseudoUint64, nil +} + +func RevealUint64(tx PrivacyMapTx, pseudo uint64) (uint64, error) { + if pseudo == 0 { + return 0, nil + } + + real, err := tx.PseudoToReal(Uint64ToStr(pseudo)) + if err != nil { + return 0, err + } + + return StrToUint64(real) +} + +func HideChanPoint(tx PrivacyMapTx, txid string, index uint32) (string, + uint32, error) { + + cp := fmt.Sprintf("%s:%d", txid, index) + pseudo, err := tx.RealToPseudo(cp) + if err != nil && err != ErrNoSuchKeyFound { + return "", 0, err + } + if err == nil { + return decodeChannelPoint(pseudo) + } + + newCp, err := NewPseudoChanPoint() + if err != nil { + return "", 0, err + } + + if err := tx.NewPair(cp, newCp); err != nil { + return "", 0, err + } + + return decodeChannelPoint(newCp) +} + +func NewPseudoChanPoint() (string, error) { + pseudoTXID, err := NewPseudoStr(txidStringLen) + if err != nil { + return "", err + } + + pseudoIndex := NewPseudoUint32() + return fmt.Sprintf("%s:%d", pseudoTXID, pseudoIndex), nil +} + +func RevealChanPoint(tx PrivacyMapTx, txid string, index uint32) (string, + uint32, error) { + + fakePoint := fmt.Sprintf("%s:%d", txid, index) + real, err := tx.PseudoToReal(fakePoint) + if err != nil { + return "", 0, err + } + + return decodeChannelPoint(real) +} + +func NewPseudoUint32() uint32 { + b := make([]byte, 4) + _, _ = rand.Read(b) + + return binary.BigEndian.Uint32(b) +} + +func HideChanPointStr(tx PrivacyMapTx, cp string) (string, error) { + txid, index, err := decodeChannelPoint(cp) + if err != nil { + return "", err + } + + newTxid, newIndex, err := HideChanPoint(tx, txid, index) + if err != nil { + return "", err + } + + return fmt.Sprintf("%s:%d", newTxid, newIndex), nil +} + +func HideBytes(tx PrivacyMapTx, realBytes []byte) ([]byte, error) { + real := hex.EncodeToString(realBytes) + + pseudo, err := HideString(tx, real) + if err != nil { + return nil, err + } + + return hex.DecodeString(pseudo) +} + +func RevealBytes(tx PrivacyMapTx, pseudoBytes []byte) ([]byte, error) { + if pseudoBytes == nil { + return nil, nil + } + + pseudo := hex.EncodeToString(pseudoBytes) + pseudo, err := RevealString(tx, pseudo) + if err != nil { + return nil, err + } + + return hex.DecodeString(pseudo) +} + +func NewPseudoUint64() (uint64, string) { + b := make([]byte, 8) + _, _ = rand.Read(b) + + i := binary.BigEndian.Uint64(b) + + return i, hex.EncodeToString(b) +} + +func Uint64ToStr(i uint64) string { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, i) + return hex.EncodeToString(b) +} + +func StrToUint64(s string) (uint64, error) { + b, err := hex.DecodeString(s) + if err != nil { + return 0, err + } + + return binary.BigEndian.Uint64(b), nil +} + +func decodeChannelPoint(cp string) (string, uint32, error) { + parts := strings.Split(cp, ":") + if len(parts) != 2 { + return "", 0, fmt.Errorf("bad channel point encoding") + } + + index, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return "", 0, err + } + + return parts[0], uint32(index), nil +} diff --git a/firewalldb/privacy_mapper_test.go b/firewalldb/privacy_mapper_test.go new file mode 100644 index 000000000..827d42c42 --- /dev/null +++ b/firewalldb/privacy_mapper_test.go @@ -0,0 +1,103 @@ +package firewalldb + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestPrivacyMapStorage tests the privacy mapper CRUD logic. +func TestPrivacyMapStorage(t *testing.T) { + tmpDir := t.TempDir() + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + pdb1 := db.PrivacyDB([4]byte{1, 1, 1, 1}) + + _ = pdb1.Update(func(tx PrivacyMapTx) error { + _, err = tx.RealToPseudo("real") + require.ErrorIs(t, err, ErrNoSuchKeyFound) + + _, err = tx.PseudoToReal("pseudo") + require.ErrorIs(t, err, ErrNoSuchKeyFound) + + err = tx.NewPair("real", "pseudo") + require.NoError(t, err) + + pseudo, err := tx.RealToPseudo("real") + require.NoError(t, err) + require.Equal(t, "pseudo", pseudo) + + real, err := tx.PseudoToReal("pseudo") + require.NoError(t, err) + require.Equal(t, "real", real) + + return nil + }) + + pdb2 := db.PrivacyDB([4]byte{2, 2, 2, 2}) + + _ = pdb2.Update(func(tx PrivacyMapTx) error { + _, err = tx.RealToPseudo("real") + require.ErrorIs(t, err, ErrNoSuchKeyFound) + + _, err = tx.PseudoToReal("pseudo") + require.ErrorIs(t, err, ErrNoSuchKeyFound) + + err = tx.NewPair("real 2", "pseudo 2") + require.NoError(t, err) + + pseudo, err := tx.RealToPseudo("real 2") + require.NoError(t, err) + require.Equal(t, "pseudo 2", pseudo) + + real, err := tx.PseudoToReal("pseudo 2") + require.NoError(t, err) + require.Equal(t, "real 2", real) + + return nil + }) +} + +// TestPrivacyMapTxs tests that the `Update` and `View` functions correctly +// provide atomic access to the db. If anything fails in the middle of an +// `Update` function, then all the changes prior should be rolled back. +func TestPrivacyMapTxs(t *testing.T) { + tmpDir := t.TempDir() + db, err := NewDB(tmpDir, "test.db") + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + pdb1 := db.PrivacyDB([4]byte{1, 1, 1, 1}) + + // Test that if an action fails midway through the transaction, then + // it is rolled back. + err = pdb1.Update(func(tx PrivacyMapTx) error { + err := tx.NewPair("real", "pseudo") + if err != nil { + return err + } + + p, err := tx.RealToPseudo("real") + if err != nil { + return err + } + require.Equal(t, "pseudo", p) + + // Now return an error. + return fmt.Errorf("random error") + }) + require.Error(t, err) + + err = pdb1.View(func(tx PrivacyMapTx) error { + _, err := tx.RealToPseudo("real") + return err + }) + require.ErrorIs(t, err, ErrNoSuchKeyFound) +} From b5d5682a6707c13f153eb49d1a1305510d7977cd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:03:29 +0200 Subject: [PATCH 13/47] rules: add Rule interface --- log.go | 6 +-- rules/config.go | 105 ++++++++++++++++++++++++++++++++++++++++++++ rules/interfaces.go | 75 +++++++++++++++++++++++++++++++ rules/log.go | 25 +++++++++++ 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 rules/config.go create mode 100644 rules/interfaces.go create mode 100644 rules/log.go diff --git a/log.go b/log.go index cb8a4cb0a..c3b90eebf 100644 --- a/log.go +++ b/log.go @@ -6,8 +6,8 @@ import ( "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" "github.com/lightninglabs/lightning-terminal/firewall" - "github.com/lightninglabs/lightning-terminal/firewalldb" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightninglabs/loop/loopd" "github.com/lightninglabs/pool" @@ -70,9 +70,7 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { lnd.AddSubLogger( root, firewall.Subsystem, intercept, firewall.UseLogger, ) - lnd.AddSubLogger( - root, firewalldb.Subsystem, intercept, firewalldb.UseLogger, - ) + lnd.AddSubLogger(root, rules.Subsystem, intercept, rules.UseLogger) // Add daemon loggers to lnd's root logger. faraday.SetupLoggers(root, intercept) diff --git a/rules/config.go b/rules/config.go new file mode 100644 index 000000000..8a79b1545 --- /dev/null +++ b/rules/config.go @@ -0,0 +1,105 @@ +package rules + +import ( + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lndclient" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// Config encompasses all the possible configuration items that could be +// required by the various rules. +type Config interface { + // GetStores can be used to get access to methods that can be used to + // perform atomic transactions on permanent and temporary local and + // global kv stores. + GetStores() firewalldb.KVStores + + // GetActionsDB can be used by rules to list any past actions that were + // made for the specific session or feature. + GetActionsDB() firewalldb.ActionsDB + + // GetMethodPerms returns a map that contains URIs and the permissions + // required to use them. + GetMethodPerms() func(string) ([]bakery.Op, bool) + + // GetNodePubKey returns the node ID of the lnd node. + GetNodePubKey() [33]byte + + // GetRouterClient returns an lnd router client. + GetRouterClient() lndclient.RouterClient + + // GetReqID is the request ID of the call being evaluated. This can be + // used to link a request with a response. + GetReqID() int64 + + // GetLndClient returns an lnd client. + GetLndClient() lndclient.LightningClient +} + +// ConfigImpl is an implementation of the Config interface. +type ConfigImpl struct { + // GetStores provides access to methods that can be used to perform + // atomic transactions on permanent and temporary local and global + // kv stores. + Stores firewalldb.KVStores + + // ActionsDB can be used by rules to list any past actions that were + // made for the specific session or feature. + ActionsDB firewalldb.ActionsDB + + // MethodPerms is a function that can be used to fetch the permissions + // required for a URI. + MethodPerms func(string) ([]bakery.Op, bool) + + // NodeID is the pub key of the lnd node. + NodeID [33]byte + + // RouterClient is an lnd router client. + RouterClient lndclient.RouterClient + + // ReqID is the request ID of the call being evaluated. This can be used + // to link a request with a response. + ReqID int64 + + // LndClient is a connection to the Lit node's LND node. + LndClient lndclient.LightningClient +} + +func (c *ConfigImpl) GetStores() firewalldb.KVStores { + return c.Stores +} + +// GetActionsDB returns the list of past actions. +func (c *ConfigImpl) GetActionsDB() firewalldb.ActionsDB { + return c.ActionsDB +} + +// GetMethodPerms returns a function that can be used to fetch the permissions +// of a URI. +func (c *ConfigImpl) GetMethodPerms() func(string) ([]bakery.Op, bool) { + return c.MethodPerms +} + +// GetNodePubKey returns the node ID for the lnd node. +func (c *ConfigImpl) GetNodePubKey() [33]byte { + return c.NodeID +} + +// GetRouterClient returns an lnd router client. +func (c *ConfigImpl) GetRouterClient() lndclient.RouterClient { + return c.RouterClient +} + +// GetReqID returns the request ID of the request or response being evaluated. +func (c *ConfigImpl) GetReqID() int64 { + return c.ReqID +} + +// GetLndClient returns an lnd client. +func (c *ConfigImpl) GetLndClient() lndclient.LightningClient { + return c.LndClient +} + +// A compile-time check to ensure that ConfigImpl implements the Config +// interface. +var _ Config = (*ConfigImpl)(nil) diff --git a/rules/interfaces.go b/rules/interfaces.go new file mode 100644 index 000000000..3d2084bd9 --- /dev/null +++ b/rules/interfaces.go @@ -0,0 +1,75 @@ +package rules + +import ( + "context" + "encoding/json" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + "google.golang.org/protobuf/proto" +) + +// Manager is the interface that any firewall rule managers will need to +// implement. A rule Manager is used to construct a rule Enforcer or rule +// Values. +type Manager interface { + // NewEnforcer constructs a new rule enforcer using the passed values + // and config. + NewEnforcer(cfg Config, values Values) (Enforcer, error) + + // NewValueFromProto converts the given proto value into a Value object. + NewValueFromProto(p *litrpc.RuleValue) (Values, error) + + // EmptyValue returns a new Values instance of the type that this + // Manager handles. + EmptyValue() Values + + // Stop cleans up the resources held by the manager. + Stop() error +} + +// Enforcer is the interface that any firewall rule enforcer must implement. +// An enforcer accepts, rejects, and possible alters an RPC proto message for a +// specific URI. +type Enforcer interface { + // HandleRequest checks the validity of a request and possibly edits it. + HandleRequest(ctx context.Context, uri string, + protoMsg proto.Message) (proto.Message, error) + + // HandleResponse handles and possibly alters a response. + HandleResponse(ctx context.Context, uri string, + protoMsg proto.Message) (proto.Message, error) + + // HandleErrorResponse handles and possibly alters a response error. + HandleErrorResponse(ctx context.Context, uri string, err error) (error, + error) +} + +// Values represents the static values that encompass the settings of the rule. +type Values interface { + // RuleName returns the name of the rule that these values are to be + // used with. + RuleName() string + + // VerifySane checks that the rules values are valid given the allowed + // minimum and maximum values. + VerifySane(minVal, maxVal Values) error + + // ToProto converts the rule Values to the litrpc counterpart. + ToProto() *litrpc.RuleValue + + // RealToPseudo converts the rule Values to a new one that uses pseudo + // keys, channel IDs, channel points etc. It returns a map of real to + // pseudo strings that should be persisted. + RealToPseudo() (Values, map[string]string, error) + + // PseudoToReal attempts to convert any appropriate pseudo fields in + // the rule Values to their corresponding real values. It uses the + // passed PrivacyMapDB to find the real values. + PseudoToReal(db firewalldb.PrivacyMapDB) (Values, error) +} + +// Marshal converts the rule Values to a json byte slice. +func Marshal(v Values) ([]byte, error) { + return json.Marshal(v) +} diff --git a/rules/log.go b/rules/log.go new file mode 100644 index 000000000..b1d5d13c3 --- /dev/null +++ b/rules/log.go @@ -0,0 +1,25 @@ +package rules + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "RULE" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger // nolint:unused + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} From c4e7284de1757537ae43d4ce69ad6a1a49fc4474 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:07:38 +0200 Subject: [PATCH 14/47] rules: add manager set --- rules/manager_set.go | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 rules/manager_set.go diff --git a/rules/manager_set.go b/rules/manager_set.go new file mode 100644 index 000000000..27b6edb5a --- /dev/null +++ b/rules/manager_set.go @@ -0,0 +1,86 @@ +package rules + +import ( + "encoding/json" + "fmt" + + "github.com/lightninglabs/lightning-terminal/litrpc" +) + +// ErrUnknownRule indicates that LiT is unaware of a values name. +var ErrUnknownRule = fmt.Errorf("unknown rule") + +// ManagerSet is a map from a rule name to a rule Manager. +type ManagerSet map[string]Manager + +// NewRuleManagerSet creates a new map of the supported rule ManagerSet. +func NewRuleManagerSet() ManagerSet { + return map[string]Manager{} +} + +// InitEnforcer gets the appropriate rule Manager for the given name and uses it +// to create an appropriate rule Enforcer. +func (m ManagerSet) InitEnforcer(cfg Config, name string, + values Values) (Enforcer, error) { + + mgr, ok := m[name] + if !ok { + return nil, ErrUnknownRule + } + + return mgr.NewEnforcer(cfg, values) +} + +// GetAllRules returns a map of names of all the rules supported by rule +// ManagerSet. +func (m ManagerSet) GetAllRules() map[string]bool { + rules := make(map[string]bool, len(m)) + for name := range m { + rules[name] = true + } + return rules +} + +// UnmarshalRuleValues identifies the appropriate rule Manager based on the +// given rule name and uses that to parse the proto value into a Value object. +func (m ManagerSet) UnmarshalRuleValues(name string, proto *litrpc.RuleValue) ( + Values, error) { + + mgr, ok := m[name] + if !ok { + return nil, ErrUnknownRule + } + + return mgr.NewValueFromProto(proto) +} + +// InitRuleValues can be used to construct a Values object given raw rule +// value bytes along with the name of the appropriate rule. +func (m ManagerSet) InitRuleValues(name string, valueBytes []byte) (Values, + error) { + + mgr, ok := m[name] + if !ok { + return nil, ErrUnknownRule + } + + v := mgr.EmptyValue() + if err := json.Unmarshal(valueBytes, v); err != nil { + return nil, err + } + + return v, nil +} + +// Stop stops all the managers in the set. +func (m ManagerSet) Stop() error { + var returnErr error + for _, mgr := range m { + err := mgr.Stop() + if err != nil { + returnErr = err + } + } + + return returnErr +} From 4c8cbe5a174e1cf2512b732c9400403ae3d38734 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:09:19 +0200 Subject: [PATCH 15/47] terminal: instantiate rule manager set --- terminal.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/terminal.go b/terminal.go index be15e7e5f..8b06da104 100644 --- a/terminal.go +++ b/terminal.go @@ -27,6 +27,7 @@ import ( "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/queue" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" @@ -154,6 +155,8 @@ type LightningTerminal struct { faradayServer *frdrpcserver.RPCServer faradayStarted bool + ruleMgrs rules.ManagerSet + loopServer *loopd.Daemon loopStarted bool @@ -246,6 +249,9 @@ func (g *LightningTerminal) Run() error { g.accountRpcServer = accounts.NewRPCServer( g.accountService, superMacBaker, ) + + g.ruleMgrs = rules.NewRuleManagerSet() + g.sessionRpcServer, err = newSessionRPCServer(&sessionRpcServerConfig{ basicAuth: g.rpcProxy.basicAuth, dbDir: filepath.Join(g.cfg.LitDir, g.cfg.Network), @@ -975,6 +981,13 @@ func (g *LightningTerminal) shutdown() error { g.middleware.Stop() } + if g.ruleMgrs != nil { + if err := g.ruleMgrs.Stop(); err != nil { + log.Errorf("Error stopping rule manager set: %v", err) + returnErr = err + } + } + if g.lndClient != nil { g.lndClient.Close() } From 042a5ee855fa8cd8bd4b3d63dbf3fe4a371e3d78 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:11:11 +0200 Subject: [PATCH 16/47] rules: add RateLimit rule --- rules/manager_set.go | 4 +- rules/rate_limit.go | 282 +++++++++++++++++++++++++++++++++++++++ rules/rate_limit_test.go | 249 ++++++++++++++++++++++++++++++++++ 3 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 rules/rate_limit.go create mode 100644 rules/rate_limit_test.go diff --git a/rules/manager_set.go b/rules/manager_set.go index 27b6edb5a..5656a7e8b 100644 --- a/rules/manager_set.go +++ b/rules/manager_set.go @@ -15,7 +15,9 @@ type ManagerSet map[string]Manager // NewRuleManagerSet creates a new map of the supported rule ManagerSet. func NewRuleManagerSet() ManagerSet { - return map[string]Manager{} + return map[string]Manager{ + RateLimitName: &RateLimitMgr{}, + } } // InitEnforcer gets the appropriate rule Manager for the given name and uses it diff --git a/rules/rate_limit.go b/rules/rate_limit.go new file mode 100644 index 000000000..440f9b406 --- /dev/null +++ b/rules/rate_limit.go @@ -0,0 +1,282 @@ +package rules + +import ( + "context" + "fmt" + "time" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + "google.golang.org/protobuf/proto" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +var ( + // Compile-time checks to ensure that RateLimit, RateLimitMgr + // and RateLimitEnforcer implement the appropriate Manager, Enforcer + // and Values interface. + _ Manager = (*RateLimitMgr)(nil) + _ Enforcer = (*RateLimitEnforcer)(nil) + _ Values = (*RateLimit)(nil) +) + +// RateLimitName is the string identifier of the RateLimitMgr values. +const RateLimitName = "rate-limit" + +// RateLimitMgr represents the rate limit values. +type RateLimitMgr struct{} + +// Stop cleans up the resources held by the manager. +// +// NOTE: This is part of the Manager interface. +func (r *RateLimitMgr) Stop() error { + return nil +} + +// NewEnforcer constructs a new RateLimit rule enforcer using the passed values +// and config. +// +// NOTE: This is part of the Manager interface. +func (r *RateLimitMgr) NewEnforcer(cfg Config, values Values) (Enforcer, + error) { + + limits, ok := values.(*RateLimit) + if !ok { + return nil, fmt.Errorf("values must be of type "+ + "RateLimit, got %T", values) + } + + return &RateLimitEnforcer{ + rateLimitConfig: cfg, + RateLimit: limits, + }, nil +} + +// NewValueFromProto converts the given proto value into a RateLimit Value +// object. +// +// NOTE: This is part of the Manager interface. +func (r *RateLimitMgr) NewValueFromProto(v *litrpc.RuleValue) (Values, error) { + rv, ok := v.Value.(*litrpc.RuleValue_RateLimit) + if !ok { + return nil, fmt.Errorf("incorrect RuleValue type") + } + + budget := rv.RateLimit + readLim := budget.ReadLimit + writeLim := budget.WriteLimit + + return &RateLimit{ + ReadLimit: &Rate{ + Iterations: readLim.Iterations, + NumHours: readLim.NumHours, + }, + WriteLimit: &Rate{ + Iterations: writeLim.Iterations, + NumHours: writeLim.NumHours, + }, + }, nil +} + +// EmptyValue returns a new RateLimit instance. +func (r *RateLimitMgr) EmptyValue() Values { + return &RateLimit{} +} + +// rateLimitConfig is the config required by RateLimitMgr. It can be derived +// from the main rules Config struct. +type rateLimitConfig interface { + GetActionsDB() firewalldb.ActionsDB + GetMethodPerms() func(string) ([]bakery.Op, bool) +} + +// RateLimitEnforcer enforces requests and responses against a RateLimit rule. +type RateLimitEnforcer struct { + rateLimitConfig + *RateLimit +} + +// HandleResponse handles and possible alters a response. This is a noop for the +// RateLimitMgr values. +// +// NOTE: this is part of the Rule interface. +func (r *RateLimitEnforcer) HandleResponse(_ context.Context, _ string, + _ proto.Message) (proto.Message, error) { + + return nil, nil +} + +// HandleRequest checks the validity of a request. It checks if the request is a +// read or a write request. Then, using the past actions DB, it determines if +// letting this request through would violate the rate limit rules. +// +// NOTE: this is part of the Rule interface. +func (r *RateLimitEnforcer) HandleRequest(ctx context.Context, uri string, + _ proto.Message) (proto.Message, error) { + + // First, we need to classify if this is a read or write call. + read := r.isRead(uri) + + // Based on the above, we can extract the relevant rate limit values + // that apply for this call. + rateLim := r.WriteLimit + if read { + rateLim = r.ReadLimit + } + + // Now we need to go and count all the previous read or write actions. + actions, err := r.GetActionsDB().ListActions(ctx) + if err != nil { + return nil, err + } + + // Determine the start time of the actions window. + startTime := time.Now().Add( + -time.Duration(rateLim.NumHours) * time.Hour, + ) + + // Now count all relevant actions which have taken place after the + // start time. + var count uint32 + for _, action := range actions { + if read != r.isRead(action.Method) { + continue + } + + if action.PerformedAt.Before(startTime) { + continue + } + + count++ + } + + if count >= rateLim.Iterations { + return nil, fmt.Errorf("too many requests received") + } + + return nil, nil +} + +// HandleErrorResponse handles and possible alters an error. This is a noop for +// the RateLimitEnforcer rule. +// +// NOTE: this is part of the Enforcer interface. +func (r *RateLimitEnforcer) HandleErrorResponse(_ context.Context, _ string, + _ error) (error, error) { + + return nil, nil +} + +// isRead is a helper that returns true if the given method/URI only requires +// read-permissions and false otherwise. +func (r *RateLimitEnforcer) isRead(method string) bool { + perms, ok := r.GetMethodPerms()(method) + if !ok { + return false + } + + for _, p := range perms { + if p.Action != "read" { + return false + } + } + return true +} + +// Rate describes a rate limit in iterations per number of hours. +type Rate struct { + Iterations uint32 `json:"iterations"` + NumHours uint32 `json:"num_hours"` +} + +// RateLimit represents the rules values. +type RateLimit struct { + WriteLimit *Rate `json:"write_limit"` + ReadLimit *Rate `json:"read_limit"` +} + +// VerifySane checks that the value of the values is ok given the min and max +// allowed values. +// +// NOTE: this is part of the Values interface. +func (r *RateLimit) VerifySane(minVal, maxVal Values) error { + minRL, ok := minVal.(*RateLimit) + if !ok { + return fmt.Errorf("min value is not of type RateLimit") + } + + maxRL, ok := maxVal.(*RateLimit) + if !ok { + return fmt.Errorf("max value is not of type RateLimit") + } + + // Check that our read limit is between the min and max. + if r.ReadLimit.lessThan(minRL.ReadLimit) || + maxRL.ReadLimit.lessThan(r.ReadLimit) { + + return fmt.Errorf("read limit is not between the min and max") + } + + // Check that our write limit is between the min and max. + if r.WriteLimit.lessThan(minRL.WriteLimit) || + maxRL.WriteLimit.lessThan(r.WriteLimit) { + + return fmt.Errorf("write limit is not between the min and max") + } + + return nil +} + +// lessThan is a helper function that checks if the current rate is less than +// another rate. +func (r *Rate) lessThan(other *Rate) bool { + return float64(r.Iterations)/float64(r.NumHours) < + float64(other.Iterations)/float64(other.NumHours) +} + +// RuleName returns the name of the rule that these values are to be used with. +// +// NOTE: this is part of the Values interface. +func (r *RateLimit) RuleName() string { + return RateLimitName +} + +// ToProto converts the rule Values to the litrpc counterpart. +// +// NOTE: this is part of the Values interface. +func (r *RateLimit) ToProto() *litrpc.RuleValue { + return &litrpc.RuleValue{ + Value: &litrpc.RuleValue_RateLimit{ + RateLimit: &litrpc.RateLimit{ + ReadLimit: &litrpc.Rate{ + Iterations: r.ReadLimit.Iterations, + NumHours: r.ReadLimit.NumHours, + }, + WriteLimit: &litrpc.Rate{ + Iterations: r.WriteLimit.Iterations, + NumHours: r.WriteLimit.NumHours, + }, + }, + }, + } +} + +// PseudoToReal attempts to convert any appropriate pseudo fields in the rule +// Values to their corresponding real values. It uses the passed PrivacyMapDB to +// find the real values. This is a no-op for the RateLimit rule. +// +// NOTE: this is part of the Values interface. +func (r *RateLimit) PseudoToReal(_ firewalldb.PrivacyMapDB) (Values, + error) { + + return r, nil +} + +// RealToPseudo converts the rule Values to a new one that uses pseudo keys, +// channel IDs, channel points etc. It returns a map of real to pseudo strings +// that should be persisted. This is a no-op for the RateLimit rule. +// +// NOTE: this is part of the Values interface. +func (r *RateLimit) RealToPseudo() (Values, map[string]string, error) { + return r, nil, nil +} diff --git a/rules/rate_limit_test.go b/rules/rate_limit_test.go new file mode 100644 index 000000000..257232b65 --- /dev/null +++ b/rules/rate_limit_test.go @@ -0,0 +1,249 @@ +package rules + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/stretchr/testify/require" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// TestRateLimitVerifySane tests that the RateLimit VerifySane method +// correctly verifies the value of the rate limit depending on given min and +// max sane values. +func TestRateLimitVerifySane(t *testing.T) { + var ( + min = &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 7, + }, + ReadLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 7, + }, + } + max = &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 24, + }, + ReadLimit: &Rate{ + Iterations: 5, + NumHours: 1, + }, + } + ) + + tests := []struct { + name string + values *RateLimit + expectErr error + }{ + { + name: "between bounds", + values: &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 48, + }, + ReadLimit: &Rate{ + Iterations: 2, + NumHours: 1, + }, + }, + }, + { + name: "read limit below bounds", + values: &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 48, + }, + ReadLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 14, + }, + }, + expectErr: fmt.Errorf("read limit is not between " + + "the min and max"), + }, + { + name: "read limit above bounds", + values: &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 48, + }, + ReadLimit: &Rate{ + Iterations: 100, + NumHours: 1, + }, + }, + expectErr: fmt.Errorf("read limit is not between " + + "the min and max"), + }, + { + name: "write limit below bounds", + values: &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 14, + }, + ReadLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 7, + }, + }, + expectErr: fmt.Errorf("write limit is not between " + + "the min and max"), + }, + { + name: "write limit above bounds", + values: &RateLimit{ + WriteLimit: &Rate{ + Iterations: 10, + NumHours: 24, + }, + ReadLimit: &Rate{ + Iterations: 1, + NumHours: 24 * 7, + }, + }, + expectErr: fmt.Errorf("write limit is not between " + + "the min and max"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.values.VerifySane(min, max) + require.Equal(t, test.expectErr, err) + }) + } +} + +// TestRateLimitCheckRequest checks that a request is correctly accepted or +// denied based on the RateLimitMgr values values. +func TestRateLimitCheckRequest(t *testing.T) { + ctx := context.Background() + + // Create a new Actions DB. + db := &mockActionsDB{} + + // Define a mock permissions map with a few read and write URIs. + perms := map[string][]bakery.Op{ + "read-uri": {{Action: "read"}}, + "write-uri": {{Action: "write"}}, + "read-write-uri": {{Action: "write"}, {Action: "read"}}, + } + + // Create a new config struct. + cfg := &mockRateLimitCfg{ + db: db, + perms: perms, + } + + // Initialise the new values. + values := &RateLimit{ + WriteLimit: &Rate{ + Iterations: 1, + NumHours: 24, + }, + ReadLimit: &Rate{ + Iterations: 2, + NumHours: 1, + }, + } + + enf := &RateLimitEnforcer{ + rateLimitConfig: cfg, + RateLimit: values, + } + + // The actions DB is currently empty. So this request should go through. + _, err := enf.HandleRequest(ctx, "write-uri", nil) + require.NoError(t, err) + + // Add a write action to the DB that took place long ago. + db.addAction("write-uri", time.Now().Add(-25*time.Hour)) + + // Since the above action took place more than 24 hours ago and the rate + // limit values defines the write-limit as 1 per 24 hours, a write call + // should still be allowed. + _, err = enf.HandleRequest(ctx, "write-uri", nil) + require.NoError(t, err) + + // Now we add a more recent write action to the DB. + db.addAction("write-uri", time.Now()) + + // Since the rate limit values only allows one write action per 24 hours, + // a request for another write action should not be allowed. + _, err = enf.HandleRequest(ctx, "write-uri", nil) + require.Error(t, err) + + // A read request should still be allowed since we have not exceeded + // the read limit yet. + _, err = enf.HandleRequest(ctx, "read-uri", nil) + require.NoError(t, err) + + // Add one read action to the db. + db.addAction("read-uri", time.Now()) + + // Since the limit is 2 read actions per hour, we should still be able + // to make another read call. + _, err = enf.HandleRequest(ctx, "read-uri", nil) + require.NoError(t, err) + + // Add one more read action to the db. + db.addAction("read-uri", time.Now()) + + // Another read call should now exceed the limit and so should not be + // allowed. + _, err = enf.HandleRequest(ctx, "read-uri", nil) + require.Error(t, err) +} + +// mockRateLimitCfg is used to mock the config backend given to the RateLimitMgr +// values during testing. +type mockRateLimitCfg struct { + db *mockActionsDB + perms map[string][]bakery.Op +} + +var _ rateLimitConfig = (*mockRateLimitCfg)(nil) + +func (m *mockRateLimitCfg) GetActionsDB() firewalldb.ActionsDB { + return m.db +} + +func (m *mockRateLimitCfg) GetMethodPerms() func(string) ([]bakery.Op, bool) { + return func(s string) ([]bakery.Op, bool) { + ops, ok := m.perms[s] + return ops, ok + } +} + +// mockActionsDB is used to mock the action's db backend used by the RateLimitMgr +// values. +type mockActionsDB struct { + actions []*firewalldb.RuleAction +} + +var _ firewalldb.ActionsDB = (*mockActionsDB)(nil) + +func (m *mockActionsDB) addAction(uri string, timestamp time.Time) { + m.actions = append(m.actions, &firewalldb.RuleAction{ + Method: uri, + PerformedAt: timestamp, + }) +} + +func (m *mockActionsDB) ListActions(_ context.Context) ( + []*firewalldb.RuleAction, error) { + + return m.actions, nil +} From ad9dd3e8ef4158d183bcbad61194967debe81586 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:13:12 +0200 Subject: [PATCH 17/47] rules: add ChanPolicyBounds rule --- rules/chan_policy_bounds.go | 334 +++++++++++++++++++++++++++ rules/chan_policy_bounds_test.go | 385 +++++++++++++++++++++++++++++++ rules/manager_set.go | 3 +- 3 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 rules/chan_policy_bounds.go create mode 100644 rules/chan_policy_bounds_test.go diff --git a/rules/chan_policy_bounds.go b/rules/chan_policy_bounds.go new file mode 100644 index 000000000..b003d35a3 --- /dev/null +++ b/rules/chan_policy_bounds.go @@ -0,0 +1,334 @@ +package rules + +import ( + "context" + "fmt" + "math" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" +) + +var ( + // Compile-time checks to ensure that ChanPolicyBounds and + // ChanPolicyBoundsMgr implement the appropriate Manager, Enforcer and + // Values interface. + _ Manager = (*ChanPolicyBoundsMgr)(nil) + _ Enforcer = (*ChanPolicyBounds)(nil) + _ Values = (*ChanPolicyBounds)(nil) +) + +// ChanPolicyBoundsName is the string identifier of the ChanPolicyBounds rule. +const ChanPolicyBoundsName = "channel-policy-bounds" + +// ChanPolicyBoundsMgr manages the ChanPolicyBounds rule. +type ChanPolicyBoundsMgr struct{} + +// Stop cleans up the resources held by the manager. +// +// NOTE: This is part of the Manager interface. +func (b *ChanPolicyBoundsMgr) Stop() error { + return nil +} + +// NewEnforcer constructs a new ChanPolicyBounds rule enforcer using the passed +// values and config. +// +// NOTE: This is part of the Manager interface. +func (b *ChanPolicyBoundsMgr) NewEnforcer(_ Config, values Values) (Enforcer, + error) { + + bounds, ok := values.(*ChanPolicyBounds) + if !ok { + return nil, fmt.Errorf("values must be of type "+ + "ChanPolicyBounds, got %T", values) + } + + return bounds, nil +} + +// NewValueFromProto converts the given proto value into a ChanPolicyBounds +// Value object. +// +// NOTE: This is part of the Manager interface. +func (b *ChanPolicyBoundsMgr) NewValueFromProto(value *litrpc.RuleValue) ( + Values, error) { + + rv, ok := value.Value.(*litrpc.RuleValue_ChanPolicyBounds) + if !ok { + return nil, fmt.Errorf("incorrect RuleValue type") + } + + policyBounds := rv.ChanPolicyBounds + + return &ChanPolicyBounds{ + MinBaseMsat: policyBounds.MinBaseMsat, + MaxBaseMsat: policyBounds.MaxBaseMsat, + MinRatePPM: policyBounds.MinRatePpm, + MaxRatePPM: policyBounds.MaxRatePpm, + MinCLTVDelta: policyBounds.MinCltvDelta, + MaxCLTVDelta: policyBounds.MaxCltvDelta, + MinHtlcMsat: policyBounds.MinHtlcMsat, + MaxHtlcMsat: policyBounds.MaxHtlcMsat, + }, nil +} + +// EmptyValue returns a new instance of ChanPolicyBounds. +// +// NOTE: This is part of the Manager interface. +func (b *ChanPolicyBoundsMgr) EmptyValue() Values { + return &ChanPolicyBounds{} +} + +// ChanPolicyBounds represents the channel policy bounds rule. +type ChanPolicyBounds struct { + // MinBaseMsat is the minimum base fee in msat that can set for a + // channel. + MinBaseMsat uint64 `json:"min_base_msat"` + + // MaxBaseMsat is the maximum base fee in msat that can set for a + // channel. + MaxBaseMsat uint64 `json:"max_base_msat"` + + // MinRatePPM is the minimum ppm fee in msat that can be set for a + // channel. + MinRatePPM uint32 `json:"min_rate_ppm"` + + // MaxRatePPM is the maximum ppm fee in msat that can be set for a + // channel. + MaxRatePPM uint32 `json:"max_rate_ppm"` + + // MinCLTVDelta is the minimum cltv delta that may set for a channel. + MinCLTVDelta uint32 `json:"min_cltv_delta"` + + // MaxCLTVDelta is the maximum cltv delta that may set for a channel. + MaxCLTVDelta uint32 `json:"max_cltv_delta"` + + // MinHtlcMsat is the minimum htlc size msat that may set for a channel. + MinHtlcMsat uint64 `json:"min_htlc_msat"` + + // MaxHtlcMsat is the maximum htlc size in msat that may be set for a + // channel. + MaxHtlcMsat uint64 `json:"max_htlc_msat"` +} + +// HandleRequest checks the validity of a request using the ChanPolicyBounds +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Enforcer interface. +func (f *ChanPolicyBounds) HandleRequest(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := f.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesRequest(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker "+ + "for URI %s does not accept request of type %v", + uri, msg.ProtoReflect().Type()) + } + + return checker.HandleRequest(ctx, msg) +} + +// HandleResponse handles and possible alters a response. This is a noop for the +// ChanPolicyBounds rule. +// +// NOTE: this is part of the Enforcer interface. +func (f *ChanPolicyBounds) HandleResponse(_ context.Context, _ string, + _ proto.Message) (proto.Message, error) { + + return nil, nil +} + +// HandleErrorResponse handles and possible alters an error. This is a noop for +// the ChanPolicyBounds rule. +// +// NOTE: this is part of the Enforcer interface. +func (f *ChanPolicyBounds) HandleErrorResponse(_ context.Context, _ string, + _ error) (error, error) { + + return nil, nil +} + +// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define +// how the URI should be handled. +func (f *ChanPolicyBounds) checkers() map[string]mid.RoundTripChecker { + return map[string]mid.RoundTripChecker{ + "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewRequestChecker( + &lnrpc.PolicyUpdateRequest{}, + &lnrpc.PolicyUpdateResponse{}, + func(ctx context.Context, + r *lnrpc.PolicyUpdateRequest) error { + + return f.checkPolicyUpdate(ctx, r) + }, + ), + } +} + +// checkPolicyUpdate verifies that the given lnrpc.PolicyUpdateRequest request +// is valid given the ChanPolicyBounds values. +func (f *ChanPolicyBounds) checkPolicyUpdate(_ context.Context, + req *lnrpc.PolicyUpdateRequest) error { + + if req.BaseFeeMsat < int64(f.MinBaseMsat) || + req.BaseFeeMsat > int64(f.MaxBaseMsat) { + + return fmt.Errorf("invalid base fee amount") + } + + if req.FeeRate == 0 && req.FeeRatePpm == 0 && f.MinRatePPM > 0 { + return fmt.Errorf("invalid fee rate") + } + + feeRate := req.FeeRatePpm + if req.FeeRate != 0 { + feeRate = uint32(math.Round(req.FeeRate * 1000000)) + } + + if feeRate < f.MinRatePPM || feeRate > f.MaxRatePPM { + return fmt.Errorf("invalid fee rate") + } + + if req.TimeLockDelta < f.MinCLTVDelta || + req.TimeLockDelta > f.MaxCLTVDelta { + + return fmt.Errorf("invalid cltv delta") + } + + if req.MinHtlcMsatSpecified { + if req.MinHtlcMsat < f.MinHtlcMsat { + return fmt.Errorf("invalid min htlc msat amount") + } + } + + if req.MaxHtlcMsat > f.MaxHtlcMsat { + return fmt.Errorf("invalid max htlc msat amount") + } + + return nil +} + +// VerifySane checks that the value of the values is ok given the min and max +// allowed values. +// +// NOTE: this is part of the Values interface. +func (f *ChanPolicyBounds) VerifySane(minVal, maxVal Values) error { + minFB, ok := minVal.(*ChanPolicyBounds) + if !ok { + return fmt.Errorf("min value is not of type ChanPolicyBounds") + } + + maxFB, ok := maxVal.(*ChanPolicyBounds) + if !ok { + return fmt.Errorf("max value is not of type ChanPolicyBounds") + } + + if !(f.MinBaseMsat >= minFB.MinBaseMsat && + f.MinBaseMsat <= maxFB.MinBaseMsat) { + + return fmt.Errorf("invalid min base fee") + } + + if !(f.MaxBaseMsat >= minFB.MaxBaseMsat && + f.MaxBaseMsat <= maxFB.MaxBaseMsat) { + + return fmt.Errorf("invalid max base fee") + } + + if !(f.MinRatePPM >= minFB.MinRatePPM && + f.MinRatePPM <= maxFB.MinRatePPM) { + + return fmt.Errorf("invalid min proportional fee") + } + + if !(f.MaxRatePPM >= minFB.MaxRatePPM && + f.MaxRatePPM <= maxFB.MaxRatePPM) { + + return fmt.Errorf("invalid max proportional fee") + } + + if !(f.MinCLTVDelta >= minFB.MinCLTVDelta && + f.MinCLTVDelta <= maxFB.MinCLTVDelta) { + + return fmt.Errorf("invalid min cltv delta") + } + + if !(f.MaxCLTVDelta >= minFB.MaxCLTVDelta && + f.MaxCLTVDelta <= maxFB.MaxCLTVDelta) { + + return fmt.Errorf("invalid max cltv delta") + } + + if !(f.MinHtlcMsat >= minFB.MinHtlcMsat && + f.MinHtlcMsat <= maxFB.MinHtlcMsat) { + + return fmt.Errorf("invalid min htlc msat amt") + } + + if !(f.MaxHtlcMsat >= minFB.MaxHtlcMsat && + f.MaxHtlcMsat <= maxFB.MaxHtlcMsat) { + + return fmt.Errorf("invalid max htlc msat amt") + } + + return nil +} + +// ToProto converts the rule Values to the litrpc counterpart. +// +// NOTE: this is part of the Values interface. +func (f *ChanPolicyBounds) ToProto() *litrpc.RuleValue { + return &litrpc.RuleValue{ + Value: &litrpc.RuleValue_ChanPolicyBounds{ + ChanPolicyBounds: &litrpc.ChannelPolicyBounds{ + MinBaseMsat: f.MinBaseMsat, + MaxBaseMsat: f.MaxBaseMsat, + MinRatePpm: f.MinRatePPM, + MaxRatePpm: f.MaxRatePPM, + MinCltvDelta: f.MinCLTVDelta, + MaxCltvDelta: f.MaxCLTVDelta, + MinHtlcMsat: f.MinHtlcMsat, + MaxHtlcMsat: f.MaxHtlcMsat, + }, + }, + } +} + +// RuleName returns the name of the rule that these values are to be used with. +// +// NOTE: this is part of the Values interface. +func (f *ChanPolicyBounds) RuleName() string { + return ChanPolicyBoundsName +} + +// PseudoToReal attempts to convert any appropriate pseudo fields in the rule +// Values to their corresponding real values. It uses the passed PrivacyMapDB to +// find the real values. This is a no-op for the ChanPolicyBounds rule. +// +// NOTE: this is part of the Values interface. +func (f *ChanPolicyBounds) PseudoToReal(_ firewalldb.PrivacyMapDB) (Values, + error) { + + return f, nil +} + +// RealToPseudo converts the rule Values to a new one that uses pseudo keys, +// channel IDs, channel points etc. It returns a map of real to pseudo strings +// that should be persisted. This is a no-op for the ChanPolicyBounds rule. +// +// NOTE: this is part of the Values interface. +func (f *ChanPolicyBounds) RealToPseudo() (Values, map[string]string, error) { + return f, nil, nil +} diff --git a/rules/chan_policy_bounds_test.go b/rules/chan_policy_bounds_test.go new file mode 100644 index 000000000..307045822 --- /dev/null +++ b/rules/chan_policy_bounds_test.go @@ -0,0 +1,385 @@ +package rules + +import ( + "context" + "fmt" + "testing" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +// TestChanPolicyBoundsVerifySane tests that the ChanPolicyBounds VerifySane +// method correctly verifies the value of the channel policy bounds depending on +// given min and max sane values. +func TestChanPolicyBoundsVerifySane(t *testing.T) { + var ( + min = &ChanPolicyBounds{ + MinBaseMsat: 5, + MaxBaseMsat: 5, + MinRatePPM: 5, + MaxRatePPM: 5, + MinCLTVDelta: 5, + MaxCLTVDelta: 5, + MinHtlcMsat: 5, + MaxHtlcMsat: 5, + } + max = &ChanPolicyBounds{ + MinBaseMsat: 10, + MaxBaseMsat: 10, + MinRatePPM: 10, + MaxRatePPM: 10, + MinCLTVDelta: 10, + MaxCLTVDelta: 10, + MinHtlcMsat: 10, + MaxHtlcMsat: 10, + } + ) + + tests := []struct { + name string + rule *ChanPolicyBounds + expectErr error + }{ + { + name: "between bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + }, + { + name: "min base msat out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 1, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid min base fee"), + }, + { + name: "max base msat out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 11, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid max base fee"), + }, + { + name: "min prop fee out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 3, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid min proportional fee"), + }, + { + name: "max prop fee out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 2, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid max proportional fee"), + }, + { + name: "min cltv delta out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 1, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid min cltv delta"), + }, + { + name: "max cltv delta out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 30, + MinHtlcMsat: 6, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid max cltv delta"), + }, + { + name: "min htlc msat out of bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 1, + MaxHtlcMsat: 6, + }, + expectErr: fmt.Errorf("invalid min htlc msat amt"), + }, + { + name: "max cltv delta of out bounds", + rule: &ChanPolicyBounds{ + MinBaseMsat: 6, + MaxBaseMsat: 6, + MinRatePPM: 6, + MaxRatePPM: 6, + MinCLTVDelta: 6, + MaxCLTVDelta: 6, + MinHtlcMsat: 6, + MaxHtlcMsat: 30, + }, + expectErr: fmt.Errorf("invalid max htlc msat amt"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.rule.VerifySane(min, max) + require.Equal(t, test.expectErr, err) + }) + } +} + +// TestChannelPolicyBoundsCheckers ensures that the ChanPolicyBounds values +// correctly accepts or denys a request. +func TestChannelPolicyBoundsCheckers(t *testing.T) { + values := &ChanPolicyBounds{ + MinBaseMsat: 5, + MaxBaseMsat: 10, + MinRatePPM: 5000000, + MaxRatePPM: 10000000, + MinCLTVDelta: 10, + MaxCLTVDelta: 40, + MinHtlcMsat: 10, + MaxHtlcMsat: 1000, + } + + tests := []struct { + name string + uri string + msg proto.Message + expectErr bool + }{ + { + name: "non policy update uri", + uri: "random-uri", + }, + { + name: "policy within bounds (using fee rate ppm)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + }, + { + name: "policy within bounds (using fee rate)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRate: 6, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + }, + { + name: "base fees too high", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 11, + FeeRate: 6, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "base fees too low", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 4, + FeeRate: 6, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "fee-rate too low (using fee-rate)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRate: 4, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "fee-rate too high (using fee-rate)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRate: 11, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "fee-rate too low (using fee-rate ppm)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 4000000, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "fee-rate too high (using fee-rate ppm)", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 11000000, + TimeLockDelta: 20, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "cltv delta too low", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 2, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "cltv delta too high", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 60, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "cltv delta too low", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 2, + MinHtlcMsat: 20, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "min htlc msat amt too low", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 60, + MinHtlcMsat: 2, + MaxHtlcMsat: 100, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + { + name: "max htlc msat amt too high", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msg: &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 6, + FeeRatePpm: 6000000, + TimeLockDelta: 60, + MinHtlcMsat: 20, + MaxHtlcMsat: 2000, + MinHtlcMsatSpecified: true, + }, + expectErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := values.HandleRequest( + context.Background(), test.uri, test.msg, + ) + if test.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/rules/manager_set.go b/rules/manager_set.go index 5656a7e8b..ae11fa4ca 100644 --- a/rules/manager_set.go +++ b/rules/manager_set.go @@ -16,7 +16,8 @@ type ManagerSet map[string]Manager // NewRuleManagerSet creates a new map of the supported rule ManagerSet. func NewRuleManagerSet() ManagerSet { return map[string]Manager{ - RateLimitName: &RateLimitMgr{}, + RateLimitName: &RateLimitMgr{}, + ChanPolicyBoundsName: &ChanPolicyBoundsMgr{}, } } From 0629601d86191d162614e3b77a842ba49d5d8126 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:13:54 +0200 Subject: [PATCH 18/47] rules: add History Limit rule --- rules/history_limit.go | 271 ++++++++++++++++++++++++++++++++++++ rules/history_limit_test.go | 130 +++++++++++++++++ rules/manager_set.go | 1 + 3 files changed, 402 insertions(+) create mode 100644 rules/history_limit.go create mode 100644 rules/history_limit_test.go diff --git a/rules/history_limit.go b/rules/history_limit.go new file mode 100644 index 000000000..1c61ba4ae --- /dev/null +++ b/rules/history_limit.go @@ -0,0 +1,271 @@ +package rules + +import ( + "context" + "fmt" + "time" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" +) + +var ( + // Compile-time checks to ensure that HistoryLimit and HistoryLimitMgr + // implement the appropriate Manager, Enforcer and Values interface. + _ Manager = (*HistoryLimitMgr)(nil) + _ Enforcer = (*HistoryLimit)(nil) + _ Values = (*HistoryLimit)(nil) +) + +// HistoryLimitName is the string identifier of the HistoryLimit rule. +const HistoryLimitName = "history-limit" + +// HistoryLimitMgr manages the History limit rule. +type HistoryLimitMgr struct{} + +// Stop cleans up the resources held by the manager. +// +// NOTE: This is part of the Manager interface. +func (h *HistoryLimitMgr) Stop() error { + return nil +} + +// NewEnforcer constructs a new HistoryLimit rule enforcer using the passed +// values and config. +// +// NOTE: This is part of the Manager interface. +func (h *HistoryLimitMgr) NewEnforcer(_ Config, values Values) (Enforcer, + error) { + + limit, ok := values.(*HistoryLimit) + if !ok { + return nil, fmt.Errorf("values must be of type HistoryLimit, "+ + "got %T", values) + } + + return limit, nil +} + +// NewValueFromProto converts the given proto value into a HistoryLimit Value +// object. +// +// NOTE: This is part of the Manager interface. +func (h *HistoryLimitMgr) NewValueFromProto(v *litrpc.RuleValue) (Values, + error) { + + rv, ok := v.Value.(*litrpc.RuleValue_HistoryLimit) + if !ok { + return nil, fmt.Errorf("incorrect RuleValue type") + } + + historyLimit := rv.HistoryLimit + + if historyLimit.StartTime != 0 && historyLimit.Duration != 0 { + return nil, fmt.Errorf("cant set both start time and duration") + } + + if historyLimit.StartTime != 0 { + return &HistoryLimit{ + StartDate: time.Unix(int64(historyLimit.StartTime), 0), + }, nil + } + + return &HistoryLimit{ + Duration: time.Second * time.Duration(historyLimit.Duration), + }, nil +} + +// EmptyValue returns a new HistoryLimit instance. +// +// NOTE: This is part of the Manager interface. +func (h *HistoryLimitMgr) EmptyValue() Values { + return &HistoryLimit{} +} + +// HistoryLimit represents the history-limit values. +type HistoryLimit struct { + StartDate time.Time `json:"start_date,omitempty"` + Duration time.Duration `json:"duration,omitempty"` +} + +// HandleRequest checks the validity of a request using the HistoryLimit +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Rule interface. +func (h *HistoryLimit) HandleRequest(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := h.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesRequest(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept request of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleRequest(ctx, msg) +} + +// HandleResponse handles a response using the HistoryLimit +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Rule interface. +func (h *HistoryLimit) HandleResponse(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := h.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesResponse(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept response of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleResponse(ctx, msg) +} + +// HandleErrorResponse handles and possible alters an error. This is a noop for +// the HistoryLimit rule. +// +// NOTE: this is part of the Enforcer interface. +func (h *HistoryLimit) HandleErrorResponse(_ context.Context, _ string, + _ error) (error, error) { + + return nil, nil +} + +// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define +// how the URI should be handled. +func (h *HistoryLimit) checkers() map[string]mid.RoundTripChecker { + return map[string]mid.RoundTripChecker{ + "/lnrpc.Lightning/ForwardingHistory": mid.NewRequestChecker( + &lnrpc.ForwardingHistoryRequest{}, + &lnrpc.ForwardingHistoryResponse{}, + func(ctx context.Context, + r *lnrpc.ForwardingHistoryRequest) error { + + startDate := h.GetStartDate() + + if r.StartTime >= uint64(startDate.Unix()) { + return nil + } + + return fmt.Errorf("can't request a start "+ + "time before %s", startDate) + }, + ), + "lnrpc.Lightning/ListInvoices": mid.NewResponseRewriter( + &lnrpc.ListInvoiceRequest{}, + &lnrpc.ListInvoiceResponse{}, + func(ctx context.Context, + r *lnrpc.ListInvoiceResponse) (proto.Message, + error) { + + startDate := h.GetStartDate() + var invoices []*lnrpc.Invoice + for _, i := range r.Invoices { + if i.CreationDate < startDate.Unix() { + continue + } + + invoices = append(invoices, i) + } + + r.Invoices = invoices + return r, nil + }, mid.PassThroughErrorHandler, + ), + } +} + +// VerifySane checks that the value of the values is ok given the min and max +// allowed values. +// +// NOTE: this is part of the Values interface. +func (h *HistoryLimit) VerifySane(minVal, _ Values) error { + minHL, ok := minVal.(*HistoryLimit) + if !ok { + return fmt.Errorf("min value is not of type HistoryLimit") + } + + if !h.GetStartDate().Before(minHL.GetStartDate()) { + minDur := time.Since(minHL.GetStartDate()) + return fmt.Errorf("history-limit start date not valid for "+ + "given the minimum required start date. Start date "+ + "should at least be %s or the duration should at "+ + "least be %s", minHL.GetStartDate(), minDur) + } + + return nil +} + +// RuleName returns the name of the rule that these values are to be used with. +// +// NOTE: this is part of the Values interface. +func (h *HistoryLimit) RuleName() string { + return HistoryLimitName +} + +// ToProto converts the rule Values to the litrpc counterpart. +// +// NOTE: this is part of the Values interface. +func (h *HistoryLimit) ToProto() *litrpc.RuleValue { + return &litrpc.RuleValue{ + Value: &litrpc.RuleValue_HistoryLimit{ + HistoryLimit: &litrpc.HistoryLimit{ + StartTime: uint64(h.StartDate.Unix()), + Duration: uint64(h.Duration.Seconds()), + }, + }, + } +} + +// GetStartDate is a helper function that determines the start date of the values +// given if a start date is set or a max duration is given. +func (h *HistoryLimit) GetStartDate() time.Time { + startDate := h.StartDate + if h.StartDate.IsZero() { + startDate = time.Now().Add(-h.Duration) + } + + return startDate +} + +// PseudoToReal attempts to convert any appropriate pseudo fields in the rule +// Values to their corresponding real values. It uses the passed PrivacyMapDB to +// find the real values. This is a no-op for the HistoryLimit rule. +// +// NOTE: this is part of the Values interface. +func (h *HistoryLimit) PseudoToReal(_ firewalldb.PrivacyMapDB) (Values, + error) { + + return h, nil +} + +// RealToPseudo converts the rule Values to a new one that uses pseudo keys, +// channel IDs, channel points etc. It returns a map of real to pseudo strings +// that should be persisted. This is a no-op for the HistoryLimit rule. +// +// NOTE: this is part of the Values interface. +func (h *HistoryLimit) RealToPseudo() (Values, map[string]string, error) { + return h, nil, nil +} diff --git a/rules/history_limit_test.go b/rules/history_limit_test.go new file mode 100644 index 000000000..eb1dacc52 --- /dev/null +++ b/rules/history_limit_test.go @@ -0,0 +1,130 @@ +package rules + +import ( + "context" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/stretchr/testify/require" +) + +// TestHistoryLimitVerifySane tests that the HistoryLimit VerifySane method +// correctly verifies the value of the rate limit depending on given min and +// max sane values. +func TestHistoryLimitVerifySane(t *testing.T) { + var min = &HistoryLimit{ + Duration: time.Hour * 24, + } + + tests := []struct { + name string + rule *HistoryLimit + expectErr bool + }{ + { + name: "between bounds (start date)", + rule: &HistoryLimit{ + StartDate: time.Now().Add(-time.Hour * 48), + }, + }, + { + name: "between bounds (duration)", + rule: &HistoryLimit{ + Duration: time.Hour * 48, + }, + }, + { + name: "too short (start date)", + rule: &HistoryLimit{ + StartDate: time.Now().Add(-time.Hour), + }, + expectErr: true, + }, + { + name: "too short (duration)", + rule: &HistoryLimit{ + Duration: time.Hour, + }, + expectErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.rule.VerifySane(min, nil) + if test.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} + +// TestHistoryLimitCheckers ensures that the HistoryLimit values correctly +// accepts or denys a request or correctly modifies a response. +func TestHistoryLimitCheckers(t *testing.T) { + values := &HistoryLimit{ + StartDate: time.Now().Add(-24 * time.Hour), + } + + ctx := context.Background() + + // A request for an irrelevant URI should be accepted. + _, err := values.HandleRequest(ctx, "random-URI", nil) + require.NoError(t, err) + + // The ForwardingHistory request has a StartTime parameter. The request + // should be allowed if the parameter is ok given the HistoryLimit values. + _, err = values.HandleRequest( + ctx, "/lnrpc.Lightning/ForwardingHistory", + &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64(time.Now().Add(-time.Hour).Unix()), + }, + ) + require.NoError(t, err) + + // And it should be denied if it violates the values. + // The ForwardingHistory request has a StartTime parameter. The request + // should be allowed if the parameter is ok given the HistoryLimit values. + _, err = values.HandleRequest( + ctx, "/lnrpc.Lightning/ForwardingHistory", + &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64( + time.Now().Add(-48 * time.Hour).Unix(), + ), + }, + ) + require.Error(t, err) + + // The ListInvoices function does not have a StartTime parameter and + // so the HistoryLimit values needs to alter the _response_ of this query + // instead to only include the invoices created after the HistoryLimit + // start date. + invoices := []*lnrpc.Invoice{ + {CreationDate: time.Now().Unix()}, + {CreationDate: time.Now().Add(-time.Hour * 5).Unix()}, + {CreationDate: time.Now().Add(-time.Hour * 25).Unix()}, + } + + respMsg, err := values.HandleResponse( + ctx, "lnrpc.Lightning/ListInvoices", + &lnrpc.ListInvoiceResponse{ + Invoices: invoices, + }, + ) + require.NoError(t, err) + require.NotNil(t, respMsg) + + resp, ok := respMsg.(*lnrpc.ListInvoiceResponse) + require.True(t, ok) + + require.Len(t, resp.Invoices, 2) + require.True(t, time.Unix(resp.Invoices[0].CreationDate, 0).After( + values.StartDate, + )) + require.True(t, time.Unix(resp.Invoices[1].CreationDate, 0).After( + values.StartDate, + )) +} diff --git a/rules/manager_set.go b/rules/manager_set.go index ae11fa4ca..52094b9e5 100644 --- a/rules/manager_set.go +++ b/rules/manager_set.go @@ -18,6 +18,7 @@ func NewRuleManagerSet() ManagerSet { return map[string]Manager{ RateLimitName: &RateLimitMgr{}, ChanPolicyBoundsName: &ChanPolicyBoundsMgr{}, + HistoryLimitName: &HistoryLimitMgr{}, } } From 8425f4572d5967f89214b0aa409a13d509f09159 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 8 Sep 2022 12:19:47 +0200 Subject: [PATCH 19/47] rules: add ChannelRestrict rule --- rules/channel_restrictions.go | 391 +++++++++++++++++++++++++++++ rules/channel_restrictions_test.go | 163 ++++++++++++ rules/manager_set.go | 1 + 3 files changed, 555 insertions(+) create mode 100644 rules/channel_restrictions.go create mode 100644 rules/channel_restrictions_test.go diff --git a/rules/channel_restrictions.go b/rules/channel_restrictions.go new file mode 100644 index 000000000..b642ab4bb --- /dev/null +++ b/rules/channel_restrictions.go @@ -0,0 +1,391 @@ +package rules + +import ( + "context" + "fmt" + "sync" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" +) + +var ( + // Compile-time checks to ensure that ChannelRestrict, + // ChannelRestrictMgr and ChannelRestrictEnforcer implement the + // appropriate Manager, Enforcer and Values interface. + _ Manager = (*ChannelRestrictMgr)(nil) + _ Enforcer = (*ChannelRestrictEnforcer)(nil) + _ Values = (*ChannelRestrict)(nil) +) + +// ChannelRestrictName is the string identifier of the ChannelRestrict rule. +const ChannelRestrictName = "channel-restriction" + +// ChannelRestrictMgr manages the ChannelRestrict rule. +type ChannelRestrictMgr struct { + // here we can have known chanID to ChanOutpoint map (and vice versa) + // then in NewEnforcer, if chan comes in we dont know of, then we + // refresh the maps. + + // chanIDToPoint is a map from channel ID to channel points for our + // known set of channels. + chanIDToPoint map[uint64]string + + // chanPointToID is a map from channel point to channel ID's for our + // known set of channels. + chanPointToID map[string]uint64 + mu sync.Mutex +} + +// NewChannelRestrictMgr constructs a new instance of a ChannelRestrictMgr. +func NewChannelRestrictMgr() *ChannelRestrictMgr { + return &ChannelRestrictMgr{ + chanIDToPoint: make(map[uint64]string), + chanPointToID: make(map[string]uint64), + } +} + +// Stop cleans up the resources held by the manager. +// +// NOTE: This is part of the Manager interface. +func (c *ChannelRestrictMgr) Stop() error { + return nil +} + +// NewEnforcer constructs a new ChannelRestrict rule enforcer using the passed +// values and config. +// +// NOTE: This is part of the Manager interface. +func (c *ChannelRestrictMgr) NewEnforcer(cfg Config, values Values) (Enforcer, + error) { + + channels, ok := values.(*ChannelRestrict) + if !ok { + return nil, fmt.Errorf("values must be of type "+ + "ChannelRestrict, got %T", values) + } + + chanMap := make(map[uint64]bool, len(channels.DenyList)) + for _, chanID := range channels.DenyList { + chanMap[chanID] = true + if err := c.maybeUpdateChannelMaps(cfg, chanID); err != nil { + return nil, err + } + } + + return &ChannelRestrictEnforcer{ + mgr: c, + ChannelRestrict: channels, + channelMap: chanMap, + }, nil +} + +// NewValueFromProto converts the given proto value into a ChannelRestrict Value +// object. +// +// NOTE: This is part of the Manager interface. +func (c *ChannelRestrictMgr) NewValueFromProto(v *litrpc.RuleValue) (Values, + error) { + + rv, ok := v.Value.(*litrpc.RuleValue_ChannelRestrict) + if !ok { + return nil, fmt.Errorf("incorrect RuleValue type") + } + + chanIDs := rv.ChannelRestrict.ChannelIds + + if len(chanIDs) == 0 { + return nil, fmt.Errorf("channel restrict list cannot be " + + "empty. If no channel restrictions should be applied " + + "then there is no need to add the rule") + } + + return &ChannelRestrict{ + DenyList: chanIDs, + }, nil +} + +// EmptyValue returns a new ChannelRestrict instance. +// +// NOTE: This is part of the Manager interface. +func (c *ChannelRestrictMgr) EmptyValue() Values { + return &ChannelRestrict{} +} + +// maybeUpdateChannelMaps updates the ChannelRestrictMgrs set of known channels +// iff the channel given by the caller is not found in the current map set. +func (c *ChannelRestrictMgr) maybeUpdateChannelMaps(cfg Config, + chanID uint64) error { + + c.mu.Lock() + defer c.mu.Unlock() + + // If we already know of this channel, we don't need to go update our + // maps. + _, ok := c.chanIDToPoint[chanID] + if ok { + return nil + } + + // Fetch a list of our open channels from LND. + lnd := cfg.GetLndClient() + chans, err := lnd.ListChannels(context.Background(), false, false) + if err != nil { + return err + } + + var ( + found bool + point string + id uint64 + ) + + // Update our set of maps and also make sure that the channel specified + // by the caller is valid given our set of open channels. + for _, channel := range chans { + point = channel.ChannelPoint + id = channel.ChannelID + + c.chanPointToID[point] = id + c.chanIDToPoint[id] = point + + if id == chanID { + found = true + } + } + + if !found { + return fmt.Errorf("invalid channel ID") + } + + return nil +} + +func (c *ChannelRestrictMgr) getChannelID(point string) (uint64, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + id, ok := c.chanPointToID[point] + return id, ok +} + +// ChannelRestrictEnforcer enforces requests and responses against a +// ChannelRestrict rule. +type ChannelRestrictEnforcer struct { + mgr *ChannelRestrictMgr + *ChannelRestrict + channelMap map[uint64]bool +} + +// HandleRequest checks the validity of a request using the ChannelRestrict +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Enforcer interface. +func (c *ChannelRestrictEnforcer) HandleRequest(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := c.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesRequest(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept request of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleRequest(ctx, msg) +} + +// HandleResponse handles a response using the ChannelRestrict +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Enforcer interface. +func (c *ChannelRestrictEnforcer) HandleResponse(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := c.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesResponse(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept response of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleResponse(ctx, msg) +} + +// HandleErrorResponse handles and possible alters an error. This is a noop for +// the ChannelRestrict rule. +// +// NOTE: this is part of the Enforcer interface. +func (c *ChannelRestrictEnforcer) HandleErrorResponse(_ context.Context, + _ string, _ error) (error, error) { + + return nil, nil +} + +// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define +// how the URI should be handled. +func (c *ChannelRestrictEnforcer) checkers() map[string]mid.RoundTripChecker { + return map[string]mid.RoundTripChecker{ + "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewRequestChecker( + &lnrpc.PolicyUpdateRequest{}, + &lnrpc.PolicyUpdateResponse{}, + func(ctx context.Context, + r *lnrpc.PolicyUpdateRequest) error { + + if r.GetGlobal() { + return fmt.Errorf("cant apply call " + + "to global scope when using " + + "a channel restriction list") + } + + chanPoint := r.GetChanPoint() + if chanPoint == nil { + return fmt.Errorf("no channel point " + + "specified") + } + + txid, err := lnrpc.GetChanPointFundingTxid( + chanPoint, + ) + if err != nil { + return err + } + + index := chanPoint.GetOutputIndex() + point := fmt.Sprintf( + "%s:%d", txid.String(), index, + ) + + id, ok := c.mgr.getChannelID(point) + if !ok { + return nil + } + + if c.channelMap[id] { + return fmt.Errorf("illegal action on " + + "channel in channel " + + "restriction list") + } + + return nil + }, + ), + } +} + +// ChannelRestrict is a rule prevents calls from acting upon a given set of +// channels. +type ChannelRestrict struct { + // DenyList is a list of SCIDs that should not be acted upon by + // any call. + DenyList []uint64 `json:"channel_deny_list"` +} + +// VerifySane checks that the value of the values is ok given the min and max +// allowed values. This is a noop for the ChannelRestrict rule. +// +// NOTE: this is part of the Values interface. +func (c *ChannelRestrict) VerifySane(_, _ Values) error { + return nil +} + +// RuleName returns the name of the rule that these values are to be used with. +// +// NOTE: this is part of the Values interface. +func (c *ChannelRestrict) RuleName() string { + return ChannelRestrictName +} + +// ToProto converts the rule Values to the litrpc counterpart. +// +// NOTE: this is part of the Values interface. +func (c *ChannelRestrict) ToProto() *litrpc.RuleValue { + return &litrpc.RuleValue{ + Value: &litrpc.RuleValue_ChannelRestrict{ + ChannelRestrict: &litrpc.ChannelRestrict{ + ChannelIds: c.DenyList, + }, + }, + } +} + +// PseudoToReal assumes that the deny-list contains pseudo channel IDs and uses +// these to check the privacy map db for the corresponding real channel IDs. +// It constructs a new ChannelRestrict instance with these real channel IDs. +// +// NOTE: this is part of the Values interface. +func (c *ChannelRestrict) PseudoToReal(db firewalldb.PrivacyMapDB) (Values, + error) { + + restrictList := make([]uint64, len(c.DenyList)) + err := db.View(func(tx firewalldb.PrivacyMapTx) error { + for i, chanID := range c.DenyList { + real, err := firewalldb.RevealUint64(tx, chanID) + if err != nil { + return err + } + + restrictList[i] = real + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return &ChannelRestrict{ + DenyList: restrictList, + }, nil +} + +// RealToPseudo converts all the channel IDs into pseudo IDs. +// +// NOTE: this is part of the Values interface. +func (c *ChannelRestrict) RealToPseudo() (Values, map[string]string, error) { + pseudoIDs := make([]uint64, len(c.DenyList)) + privMapPairs := make(map[string]string) + for i, c := range c.DenyList { + // TODO(elle): check that this channel actually exists + + chanID := firewalldb.Uint64ToStr(c) + if pseudo, ok := privMapPairs[chanID]; ok { + p, err := firewalldb.StrToUint64(pseudo) + if err != nil { + return nil, nil, err + } + + pseudoIDs[i] = p + continue + } + + pseudoCp, pseudoCpStr := firewalldb.NewPseudoUint64() + privMapPairs[chanID] = pseudoCpStr + pseudoIDs[i] = pseudoCp + } + + return &ChannelRestrict{ + DenyList: pseudoIDs, + }, privMapPairs, nil +} diff --git a/rules/channel_restrictions_test.go b/rules/channel_restrictions_test.go new file mode 100644 index 000000000..8bb5c811e --- /dev/null +++ b/rules/channel_restrictions_test.go @@ -0,0 +1,163 @@ +package rules + +import ( + "context" + "encoding/hex" + "fmt" + "math/rand" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/stretchr/testify/require" +) + +// TestChannelRestrictCheckRequest ensures that the ChannelRestrictEnforcer +// correctly accepts or denys a request. +func TestChannelRestrictCheckRequest(t *testing.T) { + txid1, index1, err := newTXID() + require.NoError(t, err) + + txid2, index2, err := newTXID() + require.NoError(t, err) + + txid3, index3, err := newTXID() + require.NoError(t, err) + + chanPointStr1 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid1), index1) + chanPointStr2 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid2), index2) + chanPointStr3 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid3), index3) + + chanID1, _ := firewalldb.NewPseudoUint64() + chanID2, _ := firewalldb.NewPseudoUint64() + chanID3, _ := firewalldb.NewPseudoUint64() + + ctx := context.Background() + mgr := NewChannelRestrictMgr() + cfg := &mockLndClient{ + channels: []lndclient.ChannelInfo{ + { + ChannelID: chanID1, + ChannelPoint: chanPointStr1, + }, + { + ChannelID: chanID2, + ChannelPoint: chanPointStr2, + }, + { + ChannelID: chanID3, + ChannelPoint: chanPointStr3, + }, + }, + } + enf, err := mgr.NewEnforcer(cfg, &ChannelRestrict{ + DenyList: []uint64{ + chanID1, chanID2, + }, + }) + require.NoError(t, err) + + // A request for an irrelevant URI should be allowed. + _, err = enf.HandleRequest(ctx, "random-URI", nil) + require.NoError(t, err) + + // If there is a channel restriction list, then no global policy updates + // are allowed. + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, + }, + ) + require.ErrorContainsf(t, err, "cant apply call to global scope when "+ + "using a channel restriction list", "") + + // Test that an action on channel point 1 in the string form is + // disallowed. + chanPoint1 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: hex.EncodeToString(txid1), + }, + OutputIndex: index1, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint1, + }, + }, + ) + require.ErrorContainsf(t, err, "illegal action on channel in channel "+ + "restriction list", "") + + // Test that an action on channel point 2 in the byte form is + // disallowed. + h, err := chainhash.NewHashFromStr(hex.EncodeToString(txid2)) + require.NoError(t, err) + + chanPoint2 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: h[:], + }, + OutputIndex: index2, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint2, + }, + }, + ) + require.ErrorContainsf(t, err, "illegal action on channel in channel "+ + "restriction list", "") + + // Test that an action on a channel not in the deny-list is allowed. + chanPoint3 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: hex.EncodeToString(txid3), + }, + OutputIndex: index3, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint3, + }, + }, + ) + require.NoError(t, err) +} + +func newTXID() ([]byte, uint32, error) { + var b [32]byte + if _, err := rand.Read(b[:]); err != nil { + return nil, 0, err + } + + return b[:], rand.Uint32(), nil +} + +type mockLndClient struct { + lndclient.LightningClient + Config + + channels []lndclient.ChannelInfo +} + +func (m *mockLndClient) GetLndClient() lndclient.LightningClient { + return m +} + +func (m *mockLndClient) ListChannels(_ context.Context, _, _ bool) ( + []lndclient.ChannelInfo, error) { + + return m.channels, nil +} diff --git a/rules/manager_set.go b/rules/manager_set.go index 52094b9e5..847ec1971 100644 --- a/rules/manager_set.go +++ b/rules/manager_set.go @@ -19,6 +19,7 @@ func NewRuleManagerSet() ManagerSet { RateLimitName: &RateLimitMgr{}, ChanPolicyBoundsName: &ChanPolicyBoundsMgr{}, HistoryLimitName: &HistoryLimitMgr{}, + ChannelRestrictName: NewChannelRestrictMgr(), } } From 0092435104e5a1ce15e81e014fd517ae68c0d229 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 8 Sep 2022 12:20:03 +0200 Subject: [PATCH 20/47] rules: add PeerRestrict rule --- rules/manager_set.go | 1 + rules/peer_restrictions.go | 395 ++++++++++++++++++++++++++++++++ rules/peer_restrictions_test.go | 152 ++++++++++++ 3 files changed, 548 insertions(+) create mode 100644 rules/peer_restrictions.go create mode 100644 rules/peer_restrictions_test.go diff --git a/rules/manager_set.go b/rules/manager_set.go index 847ec1971..027bb602b 100644 --- a/rules/manager_set.go +++ b/rules/manager_set.go @@ -20,6 +20,7 @@ func NewRuleManagerSet() ManagerSet { ChanPolicyBoundsName: &ChanPolicyBoundsMgr{}, HistoryLimitName: &HistoryLimitMgr{}, ChannelRestrictName: NewChannelRestrictMgr(), + PeersRestrictName: NewPeerRestrictMgr(), } } diff --git a/rules/peer_restrictions.go b/rules/peer_restrictions.go new file mode 100644 index 000000000..9d3b287b8 --- /dev/null +++ b/rules/peer_restrictions.go @@ -0,0 +1,395 @@ +package rules + +import ( + "context" + "fmt" + "sync" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" +) + +var ( + // Compile-time checks to ensure that PeerRestrictMgr, + // PeerRestrict and PeerRestrictEnforcer implement the + // appropriate Manager, Enforcer and Values interface. + _ Manager = (*PeerRestrictMgr)(nil) + _ Enforcer = (*PeerRestrictEnforcer)(nil) + _ Values = (*PeerRestrict)(nil) +) + +// PeersRestrictName is the string identifier of the PeerRestrict rule. +const PeersRestrictName = "peer-restriction" + +// PeerRestrictMgr manages the PeerRestrict rule. +type PeerRestrictMgr struct { + chanPointToPeerID map[string]string + peerIDToChanPoint map[string]map[string]bool + mu sync.Mutex +} + +// NewPeerRestrictMgr constructs a new PeerRestrictMgr. +func NewPeerRestrictMgr() *PeerRestrictMgr { + return &PeerRestrictMgr{ + chanPointToPeerID: make(map[string]string), + peerIDToChanPoint: make(map[string]map[string]bool), + } +} + +// Stop cleans up the resources held by the manager. +// +// NOTE: This is part of the Manager interface. +func (c *PeerRestrictMgr) Stop() error { + return nil +} + +// NewEnforcer constructs a new PeerRestrict rule enforcer using the passed +// values and config. +// +// NOTE: This is part of the Manager interface. +func (c *PeerRestrictMgr) NewEnforcer(cfg Config, values Values) (Enforcer, + error) { + + peers, ok := values.(*PeerRestrict) + if !ok { + return nil, fmt.Errorf("values must be of type "+ + "PeerRestrict, got %T", values) + } + + peerMap := make(map[string]bool, len(peers.DenyList)) + for _, peerID := range peers.DenyList { + peerMap[peerID] = true + if err := c.maybeUpdateMaps(cfg, peerID); err != nil { + return nil, err + } + } + + return &PeerRestrictEnforcer{ + cfg: cfg, + mgr: c, + PeerRestrict: peers, + peerMap: peerMap, + }, nil +} + +// NewValueFromProto converts the given proto value into a PeerRestrict Value +// object. +// +// NOTE: This is part of the Manager interface. +func (c *PeerRestrictMgr) NewValueFromProto(v *litrpc.RuleValue) (Values, + error) { + + rv, ok := v.Value.(*litrpc.RuleValue_PeerRestrict) + if !ok { + return nil, fmt.Errorf("incorrect RuleValue type") + } + + peerIDs := rv.PeerRestrict.PeerIds + + if len(peerIDs) == 0 { + return nil, fmt.Errorf("peer restrict list cannot be " + + "empty. If no channel restrictions should be applied " + + "then there is no need to add the rule") + } + + return &PeerRestrict{ + DenyList: peerIDs, + }, nil +} + +// EmptyValue returns a new PeerRestrict instance. +// +// NOTE: This is part of the Manager interface. +func (c *PeerRestrictMgr) EmptyValue() Values { + return &PeerRestrict{} +} + +// maybeUpdateMaps updates the managers peer-to-channel and channel-to-peer maps +// if the given peer ID is unknown to the manager. +func (c *PeerRestrictMgr) maybeUpdateMaps(cfg peerRestrictCfg, + id string) error { + + c.mu.Lock() + defer c.mu.Unlock() + + if _, ok := c.peerIDToChanPoint[id]; ok { + return nil + } + + return c.updateMapsUnsafe(cfg) +} + +// updateMapsUnsafe updates the manager's peer-to-channel and channel-to-peer +// maps. It is not thread safe and so must only be called if the manager's +// mutex is being held. +func (c *PeerRestrictMgr) updateMapsUnsafe(cfg peerRestrictCfg) error { + lnd := cfg.GetLndClient() + chans, err := lnd.ListChannels(context.Background(), false, false) + if err != nil { + return err + } + + c.chanPointToPeerID = make(map[string]string) + c.peerIDToChanPoint = make(map[string]map[string]bool) + + for _, channel := range chans { + peerID := channel.PubKeyBytes.String() + _, ok := c.peerIDToChanPoint[peerID] + if !ok { + c.peerIDToChanPoint[peerID] = make(map[string]bool) + } + + c.peerIDToChanPoint[peerID][channel.ChannelPoint] = true + c.chanPointToPeerID[channel.ChannelPoint] = peerID + } + + return nil +} + +func (c *PeerRestrictMgr) getPeerFromChanPoint(cfg peerRestrictCfg, + cp string) (string, bool, error) { + + c.mu.Lock() + defer c.mu.Unlock() + + peer, ok := c.chanPointToPeerID[cp] + if ok { + return peer, ok, nil + } + + err := c.updateMapsUnsafe(cfg) + if err != nil { + return "", false, err + } + + peer, ok = c.chanPointToPeerID[cp] + return peer, ok, nil +} + +// peerRestrictCfg is the config required by PeerRestrictMgr. It can be derived +// from the main rules Config struct. +type peerRestrictCfg interface { + GetLndClient() lndclient.LightningClient +} + +// PeerRestrictEnforcer enforces requests and responses against a PeerRestrict +// rule. +type PeerRestrictEnforcer struct { + mgr *PeerRestrictMgr + cfg peerRestrictCfg + *PeerRestrict + + peerMap map[string]bool +} + +// HandleRequest checks the validity of a request using the PeerRestrict +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Enforcer interface. +func (c *PeerRestrictEnforcer) HandleRequest(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := c.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesRequest(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept request of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleRequest(ctx, msg) +} + +// HandleResponse handles a response using the PeerRestrict +// rpcmiddleware.RoundTripCheckers. +// +// NOTE: this is part of the Enforcer interface. +func (c *PeerRestrictEnforcer) HandleResponse(ctx context.Context, uri string, + msg proto.Message) (proto.Message, error) { + + checkers := c.checkers() + if checkers == nil { + return nil, nil + } + + checker, ok := checkers[uri] + if !ok { + return nil, nil + } + + if !checker.HandlesResponse(msg.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept response of type %v", uri, + msg.ProtoReflect().Type()) + } + + return checker.HandleResponse(ctx, msg) +} + +// HandleErrorResponse handles and possible alters an error. This is a noop for +// the PeerRestrict rule. +// +// NOTE: this is part of the Enforcer interface. +func (c *PeerRestrictEnforcer) HandleErrorResponse(_ context.Context, + _ string, _ error) (error, error) { + + return nil, nil +} + +// checkers returns a map of URI to rpcmiddleware.RoundTripChecker which define +// how the URI should be handled. +func (c *PeerRestrictEnforcer) checkers() map[string]mid.RoundTripChecker { + return map[string]mid.RoundTripChecker{ + "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewRequestChecker( + &lnrpc.PolicyUpdateRequest{}, + &lnrpc.PolicyUpdateResponse{}, + func(ctx context.Context, + r *lnrpc.PolicyUpdateRequest) error { + + if r.GetGlobal() { + return fmt.Errorf("cant apply call " + + "to global scope when using " + + "a peer restriction list") + } + + chanPoint := r.GetChanPoint() + if chanPoint == nil { + return fmt.Errorf("no channel point " + + "specified") + } + + txid, err := lnrpc.GetChanPointFundingTxid( + chanPoint, + ) + if err != nil { + return err + } + + index := chanPoint.GetOutputIndex() + point := fmt.Sprintf("%s:%d", txid, index) + + peerID, ok, err := c.mgr.getPeerFromChanPoint( + c.cfg, point, + ) + if err != nil { + return err + } else if !ok { + return nil + } + + if c.peerMap[peerID] { + return fmt.Errorf("illegal action on " + + "peer in peer restriction " + + "list") + } + + return nil + }, + ), + } +} + +// PeerRestrict is a rule prevents calls from acting upon a given set of peers. +type PeerRestrict struct { + // DenyList is a list of peer ids that should not be acted upon by any + // call. + DenyList []string `json:"peer_deny_list"` +} + +// VerifySane checks that the value of the values is ok given the min and max +// allowed values. This is a noop for the PeerRestrict rule. +// +// NOTE: this is part of the Values interface. +func (c *PeerRestrict) VerifySane(_, _ Values) error { + return nil +} + +// RuleName returns the name of the rule that these values are to be used with. +// +// NOTE: this is part of the Values interface. +func (c *PeerRestrict) RuleName() string { + return PeersRestrictName +} + +// ToProto converts the rule Values to the litrpc counterpart. +// +// NOTE: this is part of the Values interface. +func (c *PeerRestrict) ToProto() *litrpc.RuleValue { + return &litrpc.RuleValue{ + Value: &litrpc.RuleValue_PeerRestrict{ + PeerRestrict: &litrpc.PeerRestrict{ + PeerIds: c.DenyList, + }, + }, + } +} + +// PseudoToReal assumes that the deny-list contains pseudo peer IDs and uses +// these to check the privacy map db for the corresponding real peer IDs. +// It constructs a new PeerRestrict instance with these real peer IDs. +// +// NOTE: this is part of the Values interface. +func (c *PeerRestrict) PseudoToReal(db firewalldb.PrivacyMapDB) (Values, + error) { + + restrictList := make([]string, len(c.DenyList)) + err := db.View(func(tx firewalldb.PrivacyMapTx) error { + for i, chanID := range c.DenyList { + real, err := firewalldb.RevealString(tx, chanID) + if err != nil { + return err + } + + restrictList[i] = real + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return &PeerRestrict{ + DenyList: restrictList, + }, nil +} + +// RealToPseudo converts all the real peer IDs into pseudo IDs. +// +// NOTE: this is part of the Values interface. +func (c *PeerRestrict) RealToPseudo() (Values, map[string]string, error) { + pseudoIDs := make([]string, len(c.DenyList)) + privMapPairs := make(map[string]string) + for i, id := range c.DenyList { + // TODO(elle): check that this peer is actually one of our + // channel peers. + + if pseudo, ok := privMapPairs[id]; ok { + pseudoIDs[i] = pseudo + continue + } + + pseudo, err := firewalldb.NewPseudoStr(len(id)) + if err != nil { + return nil, nil, err + } + + privMapPairs[id] = pseudo + pseudoIDs[i] = pseudo + } + + return &PeerRestrict{DenyList: pseudoIDs}, privMapPairs, nil +} diff --git a/rules/peer_restrictions_test.go b/rules/peer_restrictions_test.go new file mode 100644 index 000000000..c14fc621c --- /dev/null +++ b/rules/peer_restrictions_test.go @@ -0,0 +1,152 @@ +package rules + +import ( + "context" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" +) + +// TestPeerRestrictCheckRequest ensures that the PeerRestrictEnforcer correctly +// accepts or denys a request. +func TestPeerRestrictCheckRequest(t *testing.T) { + txid1, index1, err := newTXID() + require.NoError(t, err) + + txid2, index2, err := newTXID() + require.NoError(t, err) + + txid3, index3, err := newTXID() + require.NoError(t, err) + + chanPointStr1 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid1), index1) + chanPointStr2 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid2), index2) + chanPointStr3 := fmt.Sprintf("%s:%d", hex.EncodeToString(txid3), index3) + + peerID1, err := firewalldb.NewPseudoStr(66) + require.NoError(t, err) + + peerID2, err := firewalldb.NewPseudoStr(66) + require.NoError(t, err) + + peerID3, err := firewalldb.NewPseudoStr(66) + require.NoError(t, err) + + peerKey1, err := route.NewVertexFromStr(peerID1) + require.NoError(t, err) + + peerKey2, err := route.NewVertexFromStr(peerID2) + require.NoError(t, err) + + peerKey3, err := route.NewVertexFromStr(peerID3) + require.NoError(t, err) + + ctx := context.Background() + mgr := NewPeerRestrictMgr() + cfg := &mockLndClient{ + channels: []lndclient.ChannelInfo{ + { + ChannelPoint: chanPointStr1, + PubKeyBytes: peerKey1, + }, + { + ChannelPoint: chanPointStr2, + PubKeyBytes: peerKey2, + }, + { + ChannelPoint: chanPointStr3, + PubKeyBytes: peerKey3, + }, + }, + } + + enf, err := mgr.NewEnforcer(cfg, &PeerRestrict{ + DenyList: []string{ + peerID1, peerID2, + }, + }) + require.NoError(t, err) + + // A request for an irrelevant URI should be allowed. + _, err = enf.HandleRequest(ctx, "random-URI", nil) + require.NoError(t, err) + + // If there is a channel restriction list, then no global policy updates + // are allowed. + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true}, + }, + ) + require.ErrorContainsf(t, err, "cant apply call to global scope "+ + "when using a peer restriction list", "") + + // Test that an action on channel point 1 in the string form is + // disallowed. + chanPoint1 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: hex.EncodeToString(txid1), + }, + OutputIndex: index1, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint1, + }, + }, + ) + require.ErrorContainsf(t, err, "illegal action on peer in peer "+ + "restriction list", "") + + // Test that an action on channel point 2 in the byte form is + // disallowed. + h, err := chainhash.NewHashFromStr(hex.EncodeToString(txid2)) + require.NoError(t, err) + + chanPoint2 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: h[:], + }, + OutputIndex: index2, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint2, + }, + }, + ) + require.ErrorContainsf(t, err, "illegal action on peer in peer "+ + "restriction list", "") + + // Test that an action on a channel not in the deny-list is allowed. + chanPoint3 := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: hex.EncodeToString(txid3), + }, + OutputIndex: index3, + } + + _, err = enf.HandleRequest( + ctx, "/lnrpc.Lightning/UpdateChannelPolicy", + &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint3, + }, + }, + ) + require.NoError(t, err) +} From fbb42e810f11e854c6d66865fb1d35c02c60d7d7 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 13:58:06 +0200 Subject: [PATCH 21/47] terminal: instantiate firewall DB --- terminal.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/terminal.go b/terminal.go index 8b06da104..ee1c46a9c 100644 --- a/terminal.go +++ b/terminal.go @@ -23,6 +23,7 @@ import ( "github.com/lightninglabs/faraday/frdrpcserver" "github.com/lightninglabs/lightning-terminal/accounts" "github.com/lightninglabs/lightning-terminal/firewall" + "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/queue" @@ -180,6 +181,8 @@ type LightningTerminal struct { accountRpcServer *accounts.RPCServer + firewallDB *firewalldb.DB + restHandler http.Handler restCancel func() } @@ -252,6 +255,12 @@ func (g *LightningTerminal) Run() error { g.ruleMgrs = rules.NewRuleManagerSet() + networkDir := filepath.Join(g.cfg.LitDir, g.cfg.Network) + g.firewallDB, err = firewalldb.NewDB(networkDir, firewalldb.DBFilename) + if err != nil { + return fmt.Errorf("error creating session DB: %v", err) + } + g.sessionRpcServer, err = newSessionRPCServer(&sessionRpcServerConfig{ basicAuth: g.rpcProxy.basicAuth, dbDir: filepath.Join(g.cfg.LitDir, g.cfg.Network), @@ -981,6 +990,13 @@ func (g *LightningTerminal) shutdown() error { g.middleware.Stop() } + if g.firewallDB != nil { + if err := g.firewallDB.Close(); err != nil { + log.Errorf("Error closing rules DB: %v", err) + returnErr = err + } + } + if g.ruleMgrs != nil { if err := g.ruleMgrs.Stop(); err != nil { log.Errorf("Error stopping rule manager set: %v", err) From b59888cfa4b3edde08936ed90833186cb3092247 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:01:03 +0200 Subject: [PATCH 22/47] multi: let request logger persist Actions --- firewall/request_logger.go | 157 +++++++++++++++++++++++++++++++++---- go.mod | 2 +- session/macaroon.go | 14 +--- terminal.go | 4 +- 4 files changed, 147 insertions(+), 30 deletions(-) diff --git a/firewall/request_logger.go b/firewall/request_logger.go index f7fe9c84a..358f4802e 100644 --- a/firewall/request_logger.go +++ b/firewall/request_logger.go @@ -3,8 +3,14 @@ package firewall import ( "context" "fmt" + "sync" + "time" + "github.com/golang/protobuf/proto" + "github.com/lightninglabs/lightning-terminal/firewalldb" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightningnetwork/lnd/lnrpc" ) @@ -13,18 +19,40 @@ const ( RequestLoggerName = "lit-macaroon-firewall-logger" ) -// A compile-time assertion that RuleEnforcer is a -// rpcmiddleware.RequestInterceptor. -var _ mid.RequestInterceptor = (*RequestLogger)(nil) +var ( + // uriSkipList is a map of URIs that we don't want to log or persist + // actions for. + uriSkipList = map[string]bool{ + // We skip the CheckMacaroonPermissions uri since this method is + // called each time a call to a non-Litd endpoint needs to be + // validated and persisting the macaroon each time bloats the + // DB. + "/lnrpc.Lightning/CheckMacaroonPermissions": true, + } + + // A compile-time assertion that RuleEnforcer is a + // rpcmiddleware.RequestInterceptor. + _ mid.RequestInterceptor = (*RequestLogger)(nil) +) // RequestLogger is a RequestInterceptor that just logs incoming RPC requests. type RequestLogger struct { - // ObservedRequests is a list of all requests that were observed because - // they contained additional meta information about their intent. - // - // TODO(guggero): Replace by persistent storage to keep a history for - // the user. - ObservedRequests []*RequestInfo + actionsDB firewalldb.ActionsWriteDB + + // reqIDToAction is a map from request ID to an ActionLocator that can + // be used to find the corresponding action. This is used so that + // requests and responses can be easily linked. The mu mutex must be + // used when accessing this map. + reqIDToAction map[uint64]*firewalldb.ActionLocator + mu sync.Mutex +} + +// NewRequestLogger creates a new RequestLogger. +func NewRequestLogger(actionsDB firewalldb.ActionsWriteDB) *RequestLogger { + return &RequestLogger{ + actionsDB: actionsDB, + reqIDToAction: make(map[uint64]*firewalldb.ActionLocator), + } } // Name returns the name of the interceptor. @@ -55,14 +83,113 @@ func (r *RequestLogger) Intercept(_ context.Context, "interception request: %v", err) } - log.Infof("RequestLogger: Intercepting %v", ri) + // If this request is for any URI in the uriSkipList map, then we do not + // log or persist it. + if uriSkipList[ri.URI] { + return mid.RPCOk(req) + } + + log.Tracef("RequestLogger: Intercepting %v", ri) + + switch ri.MWRequestType { + case MWRequestTypeStreamAuth: + return mid.RPCOk(req) + + // Parse incoming requests and act on them. + case MWRequestTypeRequest: + return mid.RPCErr(req, r.addNewAction(ri)) + + // Parse and possibly manipulate outgoing responses. + case MWRequestTypeResponse: + var ( + state = firewalldb.ActionStateDone + errReason string + ) + if ri.IsError { + state = firewalldb.ActionStateError + errReason = mid.ParseResponseErr(ri.Serialized).Error() + } + + return mid.RPCErr( + req, r.MarkAction(ri.RequestID, state, errReason), + ) + + default: + return mid.RPCErrString(req, "invalid intercept type: %v", r) + } +} + +// addNewAction persists the new action to the db. +func (r *RequestLogger) addNewAction(ri *RequestInfo) error { + // If no macaroon is provided, then an empty 4-byte array is used as the + // session ID. Otherwise, the macaroon is used to derive a session ID. + var sessionID [4]byte + if ri.Macaroon != nil { + var err error + sessionID, err = session.IDFromMacaroon(ri.Macaroon) + if err != nil { + return fmt.Errorf("could not extract ID from macaroon") + } + } + + msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized) + if err != nil { + return err + } + + jsonMarshaler := &jsonpb.Marshaler{ + EmitDefaults: true, + OrigName: true, + } + + jsonStr, err := jsonMarshaler.MarshalToString(proto.MessageV1(msg)) + if err != nil { + return fmt.Errorf("unable to decode response: %v", err) + } + + action := &firewalldb.Action{ + RPCMethod: ri.URI, + RPCParamsJson: []byte(jsonStr), + AttemptedAt: time.Now(), + State: firewalldb.ActionStateInit, + } - // Persist the observed request if it is tagged with specific meta - // information. if ri.MetaInfo != nil { - r.ObservedRequests = append(r.ObservedRequests, ri) + action.ActorName = ri.MetaInfo.ActorName + action.FeatureName = ri.MetaInfo.Feature + action.Trigger = ri.MetaInfo.Trigger + action.Intent = ri.MetaInfo.Intent + action.StructuredJsonData = ri.MetaInfo.StructuredJsonData + } + + id, err := r.actionsDB.AddAction(sessionID, action) + if err != nil { + return err + } + + r.mu.Lock() + r.reqIDToAction[ri.RequestID] = &firewalldb.ActionLocator{ + SessionID: sessionID, + ActionID: id, + } + r.mu.Unlock() + + return nil +} + +// MarkAction can be used to set the state of an action identified by the given +// requestID. +func (r *RequestLogger) MarkAction(reqID uint64, + state firewalldb.ActionState, errReason string) error { + + r.mu.Lock() + defer r.mu.Unlock() + + actionLocator, ok := r.reqIDToAction[reqID] + if !ok { + return nil } + delete(r.reqIDToAction, reqID) - // Send empty response, accepting the request. - return mid.RPCOk(req) + return r.actionsDB.SetActionState(actionLocator, state, errReason) } diff --git a/go.mod b/go.mod index 7d882f689..90267ef72 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcwallet/walletdb v1.4.0 github.com/go-errors/errors v1.0.1 + github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 github.com/improbable-eng/grpc-web v0.12.0 github.com/jessevdk/go-flags v1.4.0 @@ -73,7 +74,6 @@ require ( github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect github.com/gorilla/websocket v1.4.2 // indirect diff --git a/session/macaroon.go b/session/macaroon.go index 99d5e7dfa..48a1e07b3 100644 --- a/session/macaroon.go +++ b/session/macaroon.go @@ -60,19 +60,7 @@ func IsSuperMacaroon(macHex string) bool { return false } - rawID := mac.Id() - if rawID[0] != byte(bakery.LatestVersion) { - return false - } - decodedID := &lnrpc.MacaroonId{} - idProto := rawID[1:] - err = proto.Unmarshal(idProto, decodedID) - if err != nil { - return false - } - - // The storage ID is a string representation of a 64bit unsigned number. - rootKeyID, err := strconv.ParseUint(string(decodedID.StorageId), 10, 64) + rootKeyID, err := RootKeyIDFromMacaroon(mac) if err != nil { return false } diff --git a/terminal.go b/terminal.go index ee1c46a9c..7c2274e50 100644 --- a/terminal.go +++ b/terminal.go @@ -673,13 +673,15 @@ func (g *LightningTerminal) startSubservers() error { } g.accountServiceStarted = true + requestLogger := firewall.NewRequestLogger(g.firewallDB) + // Start the middleware manager. log.Infof("Starting LiT middleware manager") g.middleware = mid.NewManager( g.cfg.RPCMiddleware.InterceptTimeout, g.lndClient.Client, g.errQueue.ChanIn(), g.accountService, - &firewall.RequestLogger{}, + requestLogger, &firewall.RuleEnforcer{}, ) From 164ade0e8c431aabc525ada4215cb6576d99bdd3 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 24 Aug 2022 09:44:18 +0200 Subject: [PATCH 23/47] multi: expose Actions This commit adds a Lit grpc service and uses it to expose a ListActions method. Litcli is also updated to make use of this method. --- cmd/litcli/actions.go | 150 +++++++++ cmd/litcli/main.go | 1 + litclient/jsoncallbacks.go | 1 + litrpc/firewall.pb.go | 645 +++++++++++++++++++++++++++++++++++++ litrpc/firewall.pb.json.go | 48 +++ litrpc/firewall.proto | 181 +++++++++++ litrpc/firewall_grpc.pb.go | 101 ++++++ perms/permissions.go | 4 + session_rpcserver.go | 163 +++++++++- terminal.go | 3 + 10 files changed, 1294 insertions(+), 3 deletions(-) create mode 100644 cmd/litcli/actions.go create mode 100644 litrpc/firewall.pb.go create mode 100644 litrpc/firewall.pb.json.go create mode 100644 litrpc/firewall.proto create mode 100644 litrpc/firewall_grpc.pb.go diff --git a/cmd/litcli/actions.go b/cmd/litcli/actions.go new file mode 100644 index 000000000..d520c4522 --- /dev/null +++ b/cmd/litcli/actions.go @@ -0,0 +1,150 @@ +package main + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/urfave/cli" +) + +var listActionsCommand = cli.Command{ + Name: "actions", + Usage: "List actions performed on the Litd server", + Action: listActions, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "feature", + Usage: "The name of the feature to " + + "filter the actions by. If " + + "left empty, then all " + + "actions will be returned.", + }, + cli.StringFlag{ + Name: "actor", + Usage: "The actor name to filter the actions by. If " + + "left empty, then all actions will be " + + "returned.", + }, + cli.StringFlag{ + Name: "method", + Usage: "The method name to filter the actions by. If " + + "left empty, then all actions will be " + + "returned.", + }, + cli.StringFlag{ + Name: "session_id", + Usage: "The session ID to filter the actions by. If " + + "left empty, then all actions will be " + + "returned.", + }, + cli.Uint64Flag{ + Name: "start_timestamp", + Usage: "Only actions executed after this unix " + + "timestamp will be considered.", + }, + cli.Uint64Flag{ + Name: "end_timestamp", + Usage: "Only actions executed before this unix " + + "timestamp will be considered.", + }, + cli.StringFlag{ + Name: "state", + Usage: "The action state to filter on. If not set, " + + "then actions of any state will be returned. " + + "Options include: 'pending', 'done' and 'error", + }, + cli.Uint64Flag{ + Name: "index_offset", + Usage: "The index of an action that will be used as " + + "either the start a query to determine which " + + "actions should be returned in the response", + }, + cli.Uint64Flag{ + Name: "max_num_actions", + Usage: "The max number of actions to return", + }, + cli.BoolFlag{ + Name: "oldest_first", + Usage: "Tf set, actions succeeding the index_offset " + + "will be returned", + }, + cli.BoolFlag{ + Name: "count_total", + Usage: "Set to true if the total number of all " + + "actions that match the given filters should " + + "be counted and returned in the request. " + + "Note that setting this will significantly " + + "decrease the performance of the query if " + + "there are many actions in the db. Also note " + + "that if this option is set, the " + + "index_offset is the index in this set of " + + "actions where as if it is not set, then " + + "index_offset is the index of the action in " + + "the db regardless of filter.", + }, + }, +} + +func listActions(ctx *cli.Context) error { + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewFirewallClient(clientConn) + + state, err := parseActionState(ctx.String("state")) + if err != nil { + return err + } + + var sessionID []byte + if ctx.String("session_id") != "" { + sessionID, err = hex.DecodeString(ctx.String("session_id")) + if err != nil { + return err + } + } + + resp, err := client.ListActions( + ctxb, &litrpc.ListActionsRequest{ + SessionId: sessionID, + FeatureName: ctx.String("feature"), + ActorName: ctx.String("actor"), + MethodName: ctx.String("method"), + State: state, + IndexOffset: ctx.Uint64("index_offset"), + MaxNumActions: ctx.Uint64("max_num_actions"), + Reversed: !ctx.Bool("oldest_first"), + CountTotal: ctx.Bool("count_total"), + StartTimestamp: ctx.Uint64("start_timestamp"), + EndTimestamp: ctx.Uint64("end_timestamp"), + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func parseActionState(actionStr string) (litrpc.ActionState, error) { + switch actionStr { + case "": + return litrpc.ActionState_STATE_UNKNOWN, nil + case "pending": + return litrpc.ActionState_STATE_PENDING, nil + case "done": + return litrpc.ActionState_STATE_DONE, nil + case "error": + return litrpc.ActionState_STATE_ERROR, nil + default: + return 0, fmt.Errorf("unknown action state %s. Valid options "+ + "include 'pending', 'done' and 'error'", actionStr) + } +} diff --git a/cmd/litcli/main.go b/cmd/litcli/main.go index 72b339a87..0163e3640 100644 --- a/cmd/litcli/main.go +++ b/cmd/litcli/main.go @@ -85,6 +85,7 @@ func main() { } app.Commands = append(app.Commands, sessionCommands...) app.Commands = append(app.Commands, accountsCommands...) + app.Commands = append(app.Commands, listActionsCommand) err := app.Run(os.Args) if err != nil { diff --git a/litclient/jsoncallbacks.go b/litclient/jsoncallbacks.go index e13a823b9..492876bf5 100644 --- a/litclient/jsoncallbacks.go +++ b/litclient/jsoncallbacks.go @@ -45,4 +45,5 @@ var Registrations = []StubPackageRegistration{ litrpc.RegisterSessionsJSONCallbacks, litrpc.RegisterAccountsJSONCallbacks, litrpc.RegisterAutopilotJSONCallbacks, + litrpc.RegisterFirewallJSONCallbacks, } diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go new file mode 100644 index 000000000..8a4e2d7c1 --- /dev/null +++ b/litrpc/firewall.pb.go @@ -0,0 +1,645 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.6.1 +// source: firewall.proto + +package litrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ActionState int32 + +const ( + // + //No state was assigned to the action. This should never be the case. + ActionState_STATE_UNKNOWN ActionState = 0 + // + //Pending means that the request resulting in the action being created + //came through but that no response came back from the appropriate backend. + //This means that the Action is either still being processed or that it + //did not successfully complete. + ActionState_STATE_PENDING ActionState = 1 + // + //Done means that the action successfully completed. + ActionState_STATE_DONE ActionState = 2 + // + //Error means that the Action did not successfully complete. + ActionState_STATE_ERROR ActionState = 3 +) + +// Enum value maps for ActionState. +var ( + ActionState_name = map[int32]string{ + 0: "STATE_UNKNOWN", + 1: "STATE_PENDING", + 2: "STATE_DONE", + 3: "STATE_ERROR", + } + ActionState_value = map[string]int32{ + "STATE_UNKNOWN": 0, + "STATE_PENDING": 1, + "STATE_DONE": 2, + "STATE_ERROR": 3, + } +) + +func (x ActionState) Enum() *ActionState { + p := new(ActionState) + *p = x + return p +} + +func (x ActionState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ActionState) Descriptor() protoreflect.EnumDescriptor { + return file_firewall_proto_enumTypes[0].Descriptor() +} + +func (ActionState) Type() protoreflect.EnumType { + return &file_firewall_proto_enumTypes[0] +} + +func (x ActionState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ActionState.Descriptor instead. +func (ActionState) EnumDescriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{0} +} + +type ListActionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The feature name which the filter the actions by. If left empty, all feature + //actions will be returned. + FeatureName string `protobuf:"bytes,1,opt,name=feature_name,json=featureName,proto3" json:"feature_name,omitempty"` + // + //The actor name to filter on. If left empty, all actor actions will be + //returned. + ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` + // + //The method name to filter on. If left empty, actions for any method will be + //returned. + MethodName string `protobuf:"bytes,3,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` + // + //The action state to filter on. If set to zero, actions for any state will + //be returned. + State ActionState `protobuf:"varint,4,opt,name=state,proto3,enum=litrpc.ActionState" json:"state,omitempty"` + // + //The index of an action that will be used as the start of a query to + //determine which actions should be returned in the response. + IndexOffset uint64 `protobuf:"varint,5,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"` + // + //The max number of actions to return in the response to this query. + MaxNumActions uint64 `protobuf:"varint,6,opt,name=max_num_actions,json=maxNumActions,proto3" json:"max_num_actions,omitempty"` + // + //If set, the actions returned will result from seeking backwards from the + //specified index offset. This can be used to paginate backwards. + Reversed bool `protobuf:"varint,7,opt,name=reversed,proto3" json:"reversed,omitempty"` + // + //Set to true if the total number of all actions that match the given filters + //should be counted and returned in the request. Note that setting this will + //significantly decrease the performance of the query if there are many + //actions in the db. + CountTotal bool `protobuf:"varint,8,opt,name=count_total,json=countTotal,proto3" json:"count_total,omitempty"` + // + //The session ID to filter on. If left empty, actions for any session will + //be returned. + SessionId []byte `protobuf:"bytes,9,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //If specified, then only actions created after the given timestamp will be + //considered. + StartTimestamp uint64 `protobuf:"varint,10,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"` + // + //If specified, then only actions created before the given timestamp will be + //considered. + EndTimestamp uint64 `protobuf:"varint,11,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"` +} + +func (x *ListActionsRequest) Reset() { + *x = ListActionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_firewall_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListActionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListActionsRequest) ProtoMessage() {} + +func (x *ListActionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_firewall_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListActionsRequest.ProtoReflect.Descriptor instead. +func (*ListActionsRequest) Descriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{0} +} + +func (x *ListActionsRequest) GetFeatureName() string { + if x != nil { + return x.FeatureName + } + return "" +} + +func (x *ListActionsRequest) GetActorName() string { + if x != nil { + return x.ActorName + } + return "" +} + +func (x *ListActionsRequest) GetMethodName() string { + if x != nil { + return x.MethodName + } + return "" +} + +func (x *ListActionsRequest) GetState() ActionState { + if x != nil { + return x.State + } + return ActionState_STATE_UNKNOWN +} + +func (x *ListActionsRequest) GetIndexOffset() uint64 { + if x != nil { + return x.IndexOffset + } + return 0 +} + +func (x *ListActionsRequest) GetMaxNumActions() uint64 { + if x != nil { + return x.MaxNumActions + } + return 0 +} + +func (x *ListActionsRequest) GetReversed() bool { + if x != nil { + return x.Reversed + } + return false +} + +func (x *ListActionsRequest) GetCountTotal() bool { + if x != nil { + return x.CountTotal + } + return false +} + +func (x *ListActionsRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *ListActionsRequest) GetStartTimestamp() uint64 { + if x != nil { + return x.StartTimestamp + } + return 0 +} + +func (x *ListActionsRequest) GetEndTimestamp() uint64 { + if x != nil { + return x.EndTimestamp + } + return 0 +} + +type ListActionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A list of actions performed by the autopilot server. + Actions []*Action `protobuf:"bytes,1,rep,name=actions,proto3" json:"actions,omitempty"` + // + //The index of the last item in the set of returned actions. This can be used + //to seek further, pagination style. + LastIndexOffset uint64 `protobuf:"varint,2,opt,name=last_index_offset,json=lastIndexOffset,proto3" json:"last_index_offset,omitempty"` + // + //The total number of actions that matched the filter in the request. It is + //only set if count_total was set in the request. + TotalCount uint64 `protobuf:"varint,3,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` +} + +func (x *ListActionsResponse) Reset() { + *x = ListActionsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_firewall_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListActionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListActionsResponse) ProtoMessage() {} + +func (x *ListActionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_firewall_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListActionsResponse.ProtoReflect.Descriptor instead. +func (*ListActionsResponse) Descriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{1} +} + +func (x *ListActionsResponse) GetActions() []*Action { + if x != nil { + return x.Actions + } + return nil +} + +func (x *ListActionsResponse) GetLastIndexOffset() uint64 { + if x != nil { + return x.LastIndexOffset + } + return 0 +} + +func (x *ListActionsResponse) GetTotalCount() uint64 { + if x != nil { + return x.TotalCount + } + return 0 +} + +type Action struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The name of the actor that initiated the action. + ActorName string `protobuf:"bytes,1,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` + // + //The name of the feature that triggered the action. + FeatureName string `protobuf:"bytes,2,opt,name=feature_name,json=featureName,proto3" json:"feature_name,omitempty"` + // + //A human readable reason that the action was performed. + Trigger string `protobuf:"bytes,3,opt,name=trigger,proto3" json:"trigger,omitempty"` + // + //A human readable string describing the intended outcome successfully + //performing the action. + Intent string `protobuf:"bytes,4,opt,name=intent,proto3" json:"intent,omitempty"` + // + //Structured info added by the action performer. + StructuredJsonData string `protobuf:"bytes,5,opt,name=structured_json_data,json=structuredJsonData,proto3" json:"structured_json_data,omitempty"` + // + //The URI of the method called. + RpcMethod string `protobuf:"bytes,6,opt,name=rpc_method,json=rpcMethod,proto3" json:"rpc_method,omitempty"` + // + //The parameters of the method call in compact json form. + RpcParamsJson string `protobuf:"bytes,7,opt,name=rpc_params_json,json=rpcParamsJson,proto3" json:"rpc_params_json,omitempty"` + // + //The unix timestamp in seconds at which the action was attempted. + Timestamp uint64 `protobuf:"varint,8,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // + //The action state. See ActionState for the meaning of each state. + State ActionState `protobuf:"varint,9,opt,name=state,proto3,enum=litrpc.ActionState" json:"state,omitempty"` + // + //If the state is Error, then this string will show the human readable reason + //for why the action errored out. + ErrorReason string `protobuf:"bytes,10,opt,name=error_reason,json=errorReason,proto3" json:"error_reason,omitempty"` + // + //The ID of the session under which the action was performed. + SessionId []byte `protobuf:"bytes,11,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` +} + +func (x *Action) Reset() { + *x = Action{} + if protoimpl.UnsafeEnabled { + mi := &file_firewall_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Action) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Action) ProtoMessage() {} + +func (x *Action) ProtoReflect() protoreflect.Message { + mi := &file_firewall_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Action.ProtoReflect.Descriptor instead. +func (*Action) Descriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{2} +} + +func (x *Action) GetActorName() string { + if x != nil { + return x.ActorName + } + return "" +} + +func (x *Action) GetFeatureName() string { + if x != nil { + return x.FeatureName + } + return "" +} + +func (x *Action) GetTrigger() string { + if x != nil { + return x.Trigger + } + return "" +} + +func (x *Action) GetIntent() string { + if x != nil { + return x.Intent + } + return "" +} + +func (x *Action) GetStructuredJsonData() string { + if x != nil { + return x.StructuredJsonData + } + return "" +} + +func (x *Action) GetRpcMethod() string { + if x != nil { + return x.RpcMethod + } + return "" +} + +func (x *Action) GetRpcParamsJson() string { + if x != nil { + return x.RpcParamsJson + } + return "" +} + +func (x *Action) GetTimestamp() uint64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *Action) GetState() ActionState { + if x != nil { + return x.State + } + return ActionState_STATE_UNKNOWN +} + +func (x *Action) GetErrorReason() string { + if x != nil { + return x.ErrorReason + } + return "" +} + +func (x *Action) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +var File_firewall_proto protoreflect.FileDescriptor + +var file_firewall_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x9f, 0x03, 0x0a, 0x12, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x4e, 0x75, + 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, + 0x72, 0x73, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, + 0x01, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x12, 0x27, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0c, 0x65, 0x6e, + 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x8c, 0x01, 0x0a, 0x13, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x11, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, + 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, 0x72, 0x75, + 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, + 0x65, 0x64, 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x70, + 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x72, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x70, 0x63, + 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x72, 0x70, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4a, 0x73, 0x6f, + 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x2a, 0x54, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, + 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, + 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x32, 0x52, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, + 0x6c, 0x6c, 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_firewall_proto_rawDescOnce sync.Once + file_firewall_proto_rawDescData = file_firewall_proto_rawDesc +) + +func file_firewall_proto_rawDescGZIP() []byte { + file_firewall_proto_rawDescOnce.Do(func() { + file_firewall_proto_rawDescData = protoimpl.X.CompressGZIP(file_firewall_proto_rawDescData) + }) + return file_firewall_proto_rawDescData +} + +var file_firewall_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_firewall_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_firewall_proto_goTypes = []interface{}{ + (ActionState)(0), // 0: litrpc.ActionState + (*ListActionsRequest)(nil), // 1: litrpc.ListActionsRequest + (*ListActionsResponse)(nil), // 2: litrpc.ListActionsResponse + (*Action)(nil), // 3: litrpc.Action +} +var file_firewall_proto_depIdxs = []int32{ + 0, // 0: litrpc.ListActionsRequest.state:type_name -> litrpc.ActionState + 3, // 1: litrpc.ListActionsResponse.actions:type_name -> litrpc.Action + 0, // 2: litrpc.Action.state:type_name -> litrpc.ActionState + 1, // 3: litrpc.Firewall.ListActions:input_type -> litrpc.ListActionsRequest + 2, // 4: litrpc.Firewall.ListActions:output_type -> litrpc.ListActionsResponse + 4, // [4:5] is the sub-list for method output_type + 3, // [3:4] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_firewall_proto_init() } +func file_firewall_proto_init() { + if File_firewall_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_firewall_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListActionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_firewall_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListActionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_firewall_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Action); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_firewall_proto_rawDesc, + NumEnums: 1, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_firewall_proto_goTypes, + DependencyIndexes: file_firewall_proto_depIdxs, + EnumInfos: file_firewall_proto_enumTypes, + MessageInfos: file_firewall_proto_msgTypes, + }.Build() + File_firewall_proto = out.File + file_firewall_proto_rawDesc = nil + file_firewall_proto_goTypes = nil + file_firewall_proto_depIdxs = nil +} diff --git a/litrpc/firewall.pb.json.go b/litrpc/firewall.pb.json.go new file mode 100644 index 000000000..c698494ba --- /dev/null +++ b/litrpc/firewall.pb.json.go @@ -0,0 +1,48 @@ +// Code generated by falafel 0.9.1. DO NOT EDIT. +// source: firewall.proto + +package litrpc + +import ( + "context" + + gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" +) + +func RegisterFirewallJSONCallbacks(registry map[string]func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error))) { + + marshaler := &gateway.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + } + + registry["litrpc.Firewall.ListActions"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &ListActionsRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewFirewallClient(conn) + resp, err := client.ListActions(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } +} diff --git a/litrpc/firewall.proto b/litrpc/firewall.proto new file mode 100644 index 000000000..8f7f5a560 --- /dev/null +++ b/litrpc/firewall.proto @@ -0,0 +1,181 @@ +syntax = "proto3"; + +package litrpc; + +option go_package = "github.com/lightninglabs/lightning-terminal/litrpc"; + +service Firewall { + rpc ListActions (ListActionsRequest) returns (ListActionsResponse); +} + +message ListActionsRequest { + /* + The feature name which the filter the actions by. If left empty, all feature + actions will be returned. + */ + string feature_name = 1; + + /* + The actor name to filter on. If left empty, all actor actions will be + returned. + */ + string actor_name = 2; + + /* + The method name to filter on. If left empty, actions for any method will be + returned. + */ + string method_name = 3; + + /* + The action state to filter on. If set to zero, actions for any state will + be returned. + */ + ActionState state = 4; + + /* + The index of an action that will be used as the start of a query to + determine which actions should be returned in the response. + */ + uint64 index_offset = 5; + + /* + The max number of actions to return in the response to this query. + */ + uint64 max_num_actions = 6; + + /* + If set, the actions returned will result from seeking backwards from the + specified index offset. This can be used to paginate backwards. + */ + bool reversed = 7; + + /* + Set to true if the total number of all actions that match the given filters + should be counted and returned in the request. Note that setting this will + significantly decrease the performance of the query if there are many + actions in the db. + */ + bool count_total = 8; + + /* + The session ID to filter on. If left empty, actions for any session will + be returned. + */ + bytes session_id = 9; + + /* + If specified, then only actions created after the given timestamp will be + considered. + */ + uint64 start_timestamp = 10 [jstype = JS_STRING]; + + /* + If specified, then only actions created before the given timestamp will be + considered. + */ + uint64 end_timestamp = 11 [jstype = JS_STRING]; +} + +message ListActionsResponse { + /* + A list of actions performed by the autopilot server. + */ + repeated Action actions = 1; + + /* + The index of the last item in the set of returned actions. This can be used + to seek further, pagination style. + */ + uint64 last_index_offset = 2; + + /* + The total number of actions that matched the filter in the request. It is + only set if count_total was set in the request. + */ + uint64 total_count = 3; +} + +message Action { + /* + The name of the actor that initiated the action. + */ + string actor_name = 1; + + /* + The name of the feature that triggered the action. + */ + string feature_name = 2; + + /* + A human readable reason that the action was performed. + */ + string trigger = 3; + + /* + A human readable string describing the intended outcome successfully + performing the action. + */ + string intent = 4; + + /* + Structured info added by the action performer. + */ + string structured_json_data = 5; + + /* + The URI of the method called. + */ + string rpc_method = 6; + + /* + The parameters of the method call in compact json form. + */ + string rpc_params_json = 7; + + /* + The unix timestamp in seconds at which the action was attempted. + */ + uint64 timestamp = 8 [jstype = JS_STRING]; + + /* + The action state. See ActionState for the meaning of each state. + */ + ActionState state = 9; + + /* + If the state is Error, then this string will show the human readable reason + for why the action errored out. + */ + string error_reason = 10; + + /* + The ID of the session under which the action was performed. + */ + bytes session_id = 11; +} + +enum ActionState { + /* + No state was assigned to the action. This should never be the case. + */ + STATE_UNKNOWN = 0; + + /* + Pending means that the request resulting in the action being created + came through but that no response came back from the appropriate backend. + This means that the Action is either still being processed or that it + did not successfully complete. + */ + STATE_PENDING = 1; + + /* + Done means that the action successfully completed. + */ + STATE_DONE = 2; + + /* + Error means that the Action did not successfully complete. + */ + STATE_ERROR = 3; +} \ No newline at end of file diff --git a/litrpc/firewall_grpc.pb.go b/litrpc/firewall_grpc.pb.go new file mode 100644 index 000000000..45d1d98c5 --- /dev/null +++ b/litrpc/firewall_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package litrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// FirewallClient is the client API for Firewall service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FirewallClient interface { + ListActions(ctx context.Context, in *ListActionsRequest, opts ...grpc.CallOption) (*ListActionsResponse, error) +} + +type firewallClient struct { + cc grpc.ClientConnInterface +} + +func NewFirewallClient(cc grpc.ClientConnInterface) FirewallClient { + return &firewallClient{cc} +} + +func (c *firewallClient) ListActions(ctx context.Context, in *ListActionsRequest, opts ...grpc.CallOption) (*ListActionsResponse, error) { + out := new(ListActionsResponse) + err := c.cc.Invoke(ctx, "/litrpc.Firewall/ListActions", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// FirewallServer is the server API for Firewall service. +// All implementations must embed UnimplementedFirewallServer +// for forward compatibility +type FirewallServer interface { + ListActions(context.Context, *ListActionsRequest) (*ListActionsResponse, error) + mustEmbedUnimplementedFirewallServer() +} + +// UnimplementedFirewallServer must be embedded to have forward compatible implementations. +type UnimplementedFirewallServer struct { +} + +func (UnimplementedFirewallServer) ListActions(context.Context, *ListActionsRequest) (*ListActionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListActions not implemented") +} +func (UnimplementedFirewallServer) mustEmbedUnimplementedFirewallServer() {} + +// UnsafeFirewallServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FirewallServer will +// result in compilation errors. +type UnsafeFirewallServer interface { + mustEmbedUnimplementedFirewallServer() +} + +func RegisterFirewallServer(s grpc.ServiceRegistrar, srv FirewallServer) { + s.RegisterService(&Firewall_ServiceDesc, srv) +} + +func _Firewall_ListActions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListActionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FirewallServer).ListActions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Firewall/ListActions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FirewallServer).ListActions(ctx, req.(*ListActionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Firewall_ServiceDesc is the grpc.ServiceDesc for Firewall service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Firewall_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "litrpc.Firewall", + HandlerType: (*FirewallServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListActions", + Handler: _Firewall_ListActions_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "firewall.proto", +} diff --git a/perms/permissions.go b/perms/permissions.go index cfc113551..e3adfd1be 100644 --- a/perms/permissions.go +++ b/perms/permissions.go @@ -62,6 +62,10 @@ var ( Entity: "account", Action: "write", }}, + "/litrpc.Lit/ListActions": {{ + Entity: "actions", + Action: "read", + }}, } // whiteListedLNDMethods is a map of all lnd RPC methods that don't diff --git a/session_rpcserver.go b/session_rpcserver.go index bd70250da..1f2a69bf2 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/perms" "github.com/lightninglabs/lightning-terminal/session" @@ -31,6 +32,7 @@ const readOnlyAction = "***readonly***" // sessionRpcServer is the gRPC server for the Session RPC interface. type sessionRpcServer struct { litrpc.UnimplementedSessionsServer + litrpc.UnimplementedFirewallServer cfg *sessionRpcServerConfig db *session.DB @@ -51,6 +53,7 @@ type sessionRpcServerConfig struct { superMacBaker session.MacaroonBaker firstConnectionDeadline time.Duration permMgr *perms.Manager + actionsDB *firewalldb.DB } // newSessionRPCServer creates a new sessionRpcServer using the passed config. @@ -86,8 +89,7 @@ func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, } // start all the components necessary for the sessionRpcServer to start serving -// requests. This includes starting the macaroon service and resuming all -// non-revoked sessions. +// requests. This includes resuming all non-revoked sessions. func (s *sessionRpcServer) start() error { // Start up all previously created sessions. sessions, err := s.db.ListSessions() @@ -96,7 +98,8 @@ func (s *sessionRpcServer) start() error { } for _, sess := range sessions { if err := s.resumeSession(sess); err != nil { - return fmt.Errorf("error resuming sesion: %v", err) + log.Errorf("error resuming session (%x): %v", + sess.LocalPublicKey.SerializeCompressed(), err) } } @@ -495,6 +498,142 @@ func (s *sessionRpcServer) RevokeSession(_ context.Context, return &litrpc.RevokeSessionResponse{}, nil } +// ListActions lists all actions attempted on the Litd server. +func (s *sessionRpcServer) ListActions(_ context.Context, + req *litrpc.ListActionsRequest) (*litrpc.ListActionsResponse, error) { + + // If no maximum number of actions is given, use a default of 100. + if req.MaxNumActions == 0 { + req.MaxNumActions = 100 + } + + // Build a filter function based on the request values. + filterFn := func(a *firewalldb.Action, reversed bool) (bool, bool) { + timeStamp := uint64(a.AttemptedAt.Unix()) + if req.EndTimestamp != 0 { + // If actions are being considered in order and the + // timestamp of this action exceeds the given end + // timestamp, then there is no need to continue + // traversing. + if !reversed && timeStamp > req.EndTimestamp { + return false, false + } + + // If the actions are in reverse order and the timestamp + // comes after the end timestamp, then the actions is + // not included but the search can continue. + if reversed && timeStamp > req.EndTimestamp { + return false, true + } + } + + if req.StartTimestamp != 0 { + // If actions are being considered in order and the + // timestamp of this action comes before the given start + // timestamp, then the action is not included but the + // search can continue. + if !reversed && timeStamp < req.StartTimestamp { + return false, true + } + + // If the actions are in reverse order and the timestamp + // comes before the start timestamp, then there is no + // need to continue traversing. + if reversed && timeStamp < req.StartTimestamp { + return false, false + } + } + + if req.FeatureName != "" && a.FeatureName != req.FeatureName { + return false, true + } + + if req.ActorName != "" && a.ActorName != req.ActorName { + return false, true + } + + if req.MethodName != "" && a.RPCMethod != req.MethodName { + return false, true + } + + if req.State != 0 { + s, err := marshalActionState(a.State) + if err != nil { + return false, true + } + + if s != req.State { + return false, true + } + } + + return true, true + } + + query := &firewalldb.ListActionsQuery{ + IndexOffset: req.IndexOffset, + MaxNum: req.MaxNumActions, + Reversed: req.Reversed, + CountAll: req.CountTotal, + } + + var ( + db = s.cfg.actionsDB + actions []*firewalldb.Action + lastIndex uint64 + totalCount uint64 + err error + ) + if req.SessionId != nil { + sessionID, err := session.IDFromBytes(req.SessionId) + if err != nil { + return nil, err + } + + actions, lastIndex, totalCount, err = db.ListSessionActions( + sessionID, filterFn, query, + ) + if err != nil { + return nil, err + } + } else { + actions, lastIndex, totalCount, err = db.ListActions( + filterFn, query, + ) + if err != nil { + return nil, err + } + } + + resp := make([]*litrpc.Action, len(actions)) + for i, a := range actions { + state, err := marshalActionState(a.State) + if err != nil { + return nil, err + } + + resp[i] = &litrpc.Action{ + SessionId: a.SessionID[:], + ActorName: a.ActorName, + FeatureName: a.FeatureName, + Trigger: a.Trigger, + Intent: a.Intent, + StructuredJsonData: a.StructuredJsonData, + RpcMethod: a.RPCMethod, + RpcParamsJson: string(a.RPCParamsJson), + Timestamp: uint64(a.AttemptedAt.Unix()), + State: state, + ErrorReason: a.ErrorReason, + } + } + + return &litrpc.ListActionsResponse{ + Actions: resp, + LastIndexOffset: lastIndex, + TotalCount: totalCount, + }, nil +} + // marshalRPCSession converts a session into its RPC counterpart. func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) { rpcState, err := marshalRPCState(sess.State) @@ -635,3 +774,21 @@ func unmarshalRPCType(typ litrpc.SessionType) (session.Type, error) { return 0, fmt.Errorf("unknown type <%d>", typ) } } + +// marshalActionState converts an Action state into its RPC counterpart. +func marshalActionState(state firewalldb.ActionState) (litrpc.ActionState, + error) { + + switch state { + case firewalldb.ActionStateUnknown: + return litrpc.ActionState_STATE_UNKNOWN, nil + case firewalldb.ActionStateInit: + return litrpc.ActionState_STATE_PENDING, nil + case firewalldb.ActionStateDone: + return litrpc.ActionState_STATE_DONE, nil + case firewalldb.ActionStateError: + return litrpc.ActionState_STATE_ERROR, nil + default: + return 0, fmt.Errorf("unknown state <%d>", state) + } +} diff --git a/terminal.go b/terminal.go index 7c2274e50..adfa6205b 100644 --- a/terminal.go +++ b/terminal.go @@ -285,6 +285,7 @@ func (g *LightningTerminal) Run() error { superMacBaker: superMacBaker, firstConnectionDeadline: g.cfg.FirstLNCConnDeadline, permMgr: g.permsMgr, + actionsDB: g.firewallDB, }) if err != nil { return fmt.Errorf("could not create new session rpc "+ @@ -741,6 +742,8 @@ func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server, litrpc.RegisterSessionsServer(server, g.sessionRpcServer) litrpc.RegisterAccountsServer(server, g.accountRpcServer) } + + litrpc.RegisterFirewallServer(server, g.sessionRpcServer) } // RegisterRestSubserver is a callback on the lnd.SubserverConfig struct that is From 9594aab54b4ef23d350f23c1165495718221aa00 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:19:21 +0200 Subject: [PATCH 24/47] autopilotserverrpc: add autopilot server protos --- autopilotserverrpc/.clang-format | 7 + autopilotserverrpc/autopilotserver.pb.go | 1302 +++++++++++++++++ autopilotserverrpc/autopilotserver.proto | 191 +++ autopilotserverrpc/autopilotserver_grpc.pb.go | 245 ++++ autopilotserverrpc/go.mod | 16 + autopilotserverrpc/go.sum | 120 ++ litrpc/gen_protos.sh | 5 + 7 files changed, 1886 insertions(+) create mode 100644 autopilotserverrpc/.clang-format create mode 100644 autopilotserverrpc/autopilotserver.pb.go create mode 100644 autopilotserverrpc/autopilotserver.proto create mode 100644 autopilotserverrpc/autopilotserver_grpc.pb.go create mode 100644 autopilotserverrpc/go.mod create mode 100644 autopilotserverrpc/go.sum diff --git a/autopilotserverrpc/.clang-format b/autopilotserverrpc/.clang-format new file mode 100644 index 000000000..f19142787 --- /dev/null +++ b/autopilotserverrpc/.clang-format @@ -0,0 +1,7 @@ +--- +Language: Proto +BasedOnStyle: Google +IndentWidth: 4 +AllowShortFunctionsOnASingleLine: None +SpaceBeforeParens: Always +CompactNamespaces: false diff --git a/autopilotserverrpc/autopilotserver.pb.go b/autopilotserverrpc/autopilotserver.pb.go new file mode 100644 index 000000000..2d25e2ddf --- /dev/null +++ b/autopilotserverrpc/autopilotserver.pb.go @@ -0,0 +1,1302 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.6.1 +// source: autopilotserver.proto + +package autopilotserverrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TermsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TermsRequest) Reset() { + *x = TermsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TermsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TermsRequest) ProtoMessage() {} + +func (x *TermsRequest) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TermsRequest.ProtoReflect.Descriptor instead. +func (*TermsRequest) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{0} +} + +type TermsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MinRequiredVersion *Version `protobuf:"bytes,1,opt,name=min_required_version,json=minRequiredVersion,proto3" json:"min_required_version,omitempty"` +} + +func (x *TermsResponse) Reset() { + *x = TermsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TermsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TermsResponse) ProtoMessage() {} + +func (x *TermsResponse) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TermsResponse.ProtoReflect.Descriptor instead. +func (*TermsResponse) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{1} +} + +func (x *TermsResponse) GetMinRequiredVersion() *Version { + if x != nil { + return x.MinRequiredVersion + } + return nil +} + +type Version struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The major application version. + Major uint32 `protobuf:"varint,1,opt,name=major,proto3" json:"major,omitempty"` + // + //The minor application version. + Minor uint32 `protobuf:"varint,2,opt,name=minor,proto3" json:"minor,omitempty"` + // + //The application patch number. + Patch uint32 `protobuf:"varint,3,opt,name=patch,proto3" json:"patch,omitempty"` +} + +func (x *Version) Reset() { + *x = Version{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Version) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Version) ProtoMessage() {} + +func (x *Version) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Version.ProtoReflect.Descriptor instead. +func (*Version) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{2} +} + +func (x *Version) GetMajor() uint32 { + if x != nil { + return x.Major + } + return 0 +} + +func (x *Version) GetMinor() uint32 { + if x != nil { + return x.Minor + } + return 0 +} + +func (x *Version) GetPatch() uint32 { + if x != nil { + return x.Patch + } + return 0 +} + +type ListFeaturesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListFeaturesRequest) Reset() { + *x = ListFeaturesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFeaturesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeaturesRequest) ProtoMessage() {} + +func (x *ListFeaturesRequest) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFeaturesRequest.ProtoReflect.Descriptor instead. +func (*ListFeaturesRequest) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{3} +} + +type ActivateSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The static public key of the client that is to be used for future noise + //handshakes. Since the autopilot is the initiator of a connection, the + //client's key is called the "responder" key. + ResponderPubKey []byte `protobuf:"bytes,1,opt,name=responder_pub_key,json=responderPubKey,proto3" json:"responder_pub_key,omitempty"` +} + +func (x *ActivateSessionRequest) Reset() { + *x = ActivateSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActivateSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivateSessionRequest) ProtoMessage() {} + +func (x *ActivateSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivateSessionRequest.ProtoReflect.Descriptor instead. +func (*ActivateSessionRequest) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{4} +} + +func (x *ActivateSessionRequest) GetResponderPubKey() []byte { + if x != nil { + return x.ResponderPubKey + } + return nil +} + +type ActivateSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The autopilot's static pub key to be used for the noise connection with the + //client. + InitiatorPubKey []byte `protobuf:"bytes,1,opt,name=initiator_pub_key,json=initiatorPubKey,proto3" json:"initiator_pub_key,omitempty"` +} + +func (x *ActivateSessionResponse) Reset() { + *x = ActivateSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActivateSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivateSessionResponse) ProtoMessage() {} + +func (x *ActivateSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivateSessionResponse.ProtoReflect.Descriptor instead. +func (*ActivateSessionResponse) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{5} +} + +func (x *ActivateSessionResponse) GetInitiatorPubKey() []byte { + if x != nil { + return x.InitiatorPubKey + } + return nil +} + +type ListFeaturesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //A map of feature name to Feature object. This map represents each of the + //features supported by the autopilot server along with the details of + //each feature. + Features map[string]*Feature `protobuf:"bytes,1,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ListFeaturesResponse) Reset() { + *x = ListFeaturesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListFeaturesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeaturesResponse) ProtoMessage() {} + +func (x *ListFeaturesResponse) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFeaturesResponse.ProtoReflect.Descriptor instead. +func (*ListFeaturesResponse) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{6} +} + +func (x *ListFeaturesResponse) GetFeatures() map[string]*Feature { + if x != nil { + return x.Features + } + return nil +} + +type Feature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The string identifier of the feature. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // + //A human-readable description of what the feature offers. + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // + //A map of the rule names to rule values that make sense for this feature. + Rules map[string]*Rule `protobuf:"bytes,3,rep,name=rules,proto3" json:"rules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // + //A list of the permissions that the feature will require to operate. + PermissionsList []*Permissions `protobuf:"bytes,4,rep,name=permissions_list,json=permissionsList,proto3" json:"permissions_list,omitempty"` +} + +func (x *Feature) Reset() { + *x = Feature{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feature) ProtoMessage() {} + +func (x *Feature) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feature.ProtoReflect.Descriptor instead. +func (*Feature) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{7} +} + +func (x *Feature) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Feature) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Feature) GetRules() map[string]*Rule { + if x != nil { + return x.Rules + } + return nil +} + +func (x *Feature) GetPermissionsList() []*Permissions { + if x != nil { + return x.PermissionsList + } + return nil +} + +type Rule struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The name of the rule. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // + //The recommended default values for the rule. These may vary from feature + //to feature. + Default []byte `protobuf:"bytes,2,opt,name=default,proto3" json:"default,omitempty"` + // + //The minimum sane value of the rule that is allowed for the associated + //feature. + MinValue []byte `protobuf:"bytes,3,opt,name=min_value,json=minValue,proto3" json:"min_value,omitempty"` + // + //The maximum sane value of the rule that is allowed for the associated + //feature. + MaxValue []byte `protobuf:"bytes,4,opt,name=max_value,json=maxValue,proto3" json:"max_value,omitempty"` +} + +func (x *Rule) Reset() { + *x = Rule{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Rule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Rule) ProtoMessage() {} + +func (x *Rule) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Rule.ProtoReflect.Descriptor instead. +func (*Rule) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{8} +} + +func (x *Rule) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Rule) GetDefault() []byte { + if x != nil { + return x.Default + } + return nil +} + +func (x *Rule) GetMinValue() []byte { + if x != nil { + return x.MinValue + } + return nil +} + +func (x *Rule) GetMaxValue() []byte { + if x != nil { + return x.MaxValue + } + return nil +} + +type Permissions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The URI of the method that the operation set is referring to. + Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` + // + //A list of operations that the method can perform + Operations []*Operation `protobuf:"bytes,2,rep,name=operations,proto3" json:"operations,omitempty"` +} + +func (x *Permissions) Reset() { + *x = Permissions{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Permissions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Permissions) ProtoMessage() {} + +func (x *Permissions) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Permissions.ProtoReflect.Descriptor instead. +func (*Permissions) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{9} +} + +func (x *Permissions) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *Permissions) GetOperations() []*Operation { + if x != nil { + return x.Operations + } + return nil +} + +type Operation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The entity to which the action applies. + Entity string `protobuf:"bytes,1,opt,name=entity,proto3" json:"entity,omitempty"` + // + //The action that can be performed on the above specified entity. + Action string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"` +} + +func (x *Operation) Reset() { + *x = Operation{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Operation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Operation) ProtoMessage() {} + +func (x *Operation) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Operation.ProtoReflect.Descriptor instead. +func (*Operation) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{10} +} + +func (x *Operation) GetEntity() string { + if x != nil { + return x.Entity + } + return "" +} + +func (x *Operation) GetAction() string { + if x != nil { + return x.Action + } + return "" +} + +type RegisterSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The static public key of the client that is to be used for future noise + //handshakes. Since the autopilot is the initiator of a connection, the + //client's key is called the "responder" key. + ResponderPubKey []byte `protobuf:"bytes,1,opt,name=responder_pub_key,json=responderPubKey,proto3" json:"responder_pub_key,omitempty"` + // + //The address of the mailbox that the client will use for the LNC connection. + MailboxAddr string `protobuf:"bytes,2,opt,name=mailbox_addr,json=mailboxAddr,proto3" json:"mailbox_addr,omitempty"` + // + //Set to true if tls should be skipped for when connecting to the mailbox. + DevServer bool `protobuf:"varint,3,opt,name=dev_server,json=devServer,proto3" json:"dev_server,omitempty"` + // + //A map from feature name to configuration bytes for that feature. + FeatureConfigs map[string][]byte `protobuf:"bytes,4,rep,name=feature_configs,json=featureConfigs,proto3" json:"feature_configs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // + //The version of the Lit client. + LitVersion *Version `protobuf:"bytes,5,opt,name=lit_version,json=litVersion,proto3" json:"lit_version,omitempty"` + // + //The version of the LND that the Lit client is using. + LndVersion *Version `protobuf:"bytes,6,opt,name=lnd_version,json=lndVersion,proto3" json:"lnd_version,omitempty"` +} + +func (x *RegisterSessionRequest) Reset() { + *x = RegisterSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterSessionRequest) ProtoMessage() {} + +func (x *RegisterSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterSessionRequest.ProtoReflect.Descriptor instead. +func (*RegisterSessionRequest) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{11} +} + +func (x *RegisterSessionRequest) GetResponderPubKey() []byte { + if x != nil { + return x.ResponderPubKey + } + return nil +} + +func (x *RegisterSessionRequest) GetMailboxAddr() string { + if x != nil { + return x.MailboxAddr + } + return "" +} + +func (x *RegisterSessionRequest) GetDevServer() bool { + if x != nil { + return x.DevServer + } + return false +} + +func (x *RegisterSessionRequest) GetFeatureConfigs() map[string][]byte { + if x != nil { + return x.FeatureConfigs + } + return nil +} + +func (x *RegisterSessionRequest) GetLitVersion() *Version { + if x != nil { + return x.LitVersion + } + return nil +} + +func (x *RegisterSessionRequest) GetLndVersion() *Version { + if x != nil { + return x.LndVersion + } + return nil +} + +type RegisterSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The autopilot's static pub key to be used for the noise connection with the + //client. + InitiatorPubKey []byte `protobuf:"bytes,1,opt,name=initiator_pub_key,json=initiatorPubKey,proto3" json:"initiator_pub_key,omitempty"` +} + +func (x *RegisterSessionResponse) Reset() { + *x = RegisterSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterSessionResponse) ProtoMessage() {} + +func (x *RegisterSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterSessionResponse.ProtoReflect.Descriptor instead. +func (*RegisterSessionResponse) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{12} +} + +func (x *RegisterSessionResponse) GetInitiatorPubKey() []byte { + if x != nil { + return x.InitiatorPubKey + } + return nil +} + +type RevokeSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The client's key for the session that the client is revoking. + ResponderPubKey []byte `protobuf:"bytes,1,opt,name=responder_pub_key,json=responderPubKey,proto3" json:"responder_pub_key,omitempty"` +} + +func (x *RevokeSessionRequest) Reset() { + *x = RevokeSessionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSessionRequest) ProtoMessage() {} + +func (x *RevokeSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSessionRequest.ProtoReflect.Descriptor instead. +func (*RevokeSessionRequest) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{13} +} + +func (x *RevokeSessionRequest) GetResponderPubKey() []byte { + if x != nil { + return x.ResponderPubKey + } + return nil +} + +type RevokeSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *RevokeSessionResponse) Reset() { + *x = RevokeSessionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_autopilotserver_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RevokeSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeSessionResponse) ProtoMessage() {} + +func (x *RevokeSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_autopilotserver_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeSessionResponse.ProtoReflect.Descriptor instead. +func (*RevokeSessionResponse) Descriptor() ([]byte, []int) { + return file_autopilotserver_proto_rawDescGZIP(), []int{14} +} + +var File_autopilotserver_proto protoreflect.FileDescriptor + +var file_autopilotserver_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, + 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x22, 0x0e, 0x0a, 0x0c, 0x54, + 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x5e, 0x0a, 0x0d, 0x54, + 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x14, + 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, + 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x6d, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x4b, 0x0a, 0x07, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x69, 0x6e, + 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, + 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x44, 0x0a, 0x16, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x50, + 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x45, 0x0a, 0x17, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x70, 0x75, + 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0xc4, 0x01, 0x0a, + 0x14, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x58, 0x0a, 0x0d, 0x46, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x9d, 0x02, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x75, + 0x6c, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x10, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0f, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x1a, + 0x52, 0x0a, 0x0a, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x6e, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x69, + 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x64, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x3d, 0x0a, 0x0a, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, + 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x3b, 0x0a, 0x09, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, + 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xae, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x70, + 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, + 0x0c, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x64, 0x65, 0x76, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, + 0x67, 0x0a, 0x0f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, + 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x3c, 0x0a, 0x0b, 0x6c, 0x69, 0x74, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6c, 0x69, 0x74, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3c, 0x0a, 0x0b, 0x6c, 0x6e, 0x64, 0x5f, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, + 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, + 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x6c, 0x6e, 0x64, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x41, 0x0a, 0x13, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x45, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x5f, + 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x42, + 0x0a, 0x14, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x22, 0x17, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xfa, 0x03, 0x0a, 0x09, + 0x41, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x12, 0x4c, 0x0a, 0x05, 0x54, 0x65, 0x72, + 0x6d, 0x73, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x46, + 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, + 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, + 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x75, 0x74, 0x6f, + 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x0f, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x61, 0x75, 0x74, 0x6f, + 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, + 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x40, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, + 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x70, 0x69, 0x6c, 0x6f, + 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_autopilotserver_proto_rawDescOnce sync.Once + file_autopilotserver_proto_rawDescData = file_autopilotserver_proto_rawDesc +) + +func file_autopilotserver_proto_rawDescGZIP() []byte { + file_autopilotserver_proto_rawDescOnce.Do(func() { + file_autopilotserver_proto_rawDescData = protoimpl.X.CompressGZIP(file_autopilotserver_proto_rawDescData) + }) + return file_autopilotserver_proto_rawDescData +} + +var file_autopilotserver_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_autopilotserver_proto_goTypes = []interface{}{ + (*TermsRequest)(nil), // 0: autopilotserverrpc.TermsRequest + (*TermsResponse)(nil), // 1: autopilotserverrpc.TermsResponse + (*Version)(nil), // 2: autopilotserverrpc.Version + (*ListFeaturesRequest)(nil), // 3: autopilotserverrpc.ListFeaturesRequest + (*ActivateSessionRequest)(nil), // 4: autopilotserverrpc.ActivateSessionRequest + (*ActivateSessionResponse)(nil), // 5: autopilotserverrpc.ActivateSessionResponse + (*ListFeaturesResponse)(nil), // 6: autopilotserverrpc.ListFeaturesResponse + (*Feature)(nil), // 7: autopilotserverrpc.Feature + (*Rule)(nil), // 8: autopilotserverrpc.Rule + (*Permissions)(nil), // 9: autopilotserverrpc.Permissions + (*Operation)(nil), // 10: autopilotserverrpc.Operation + (*RegisterSessionRequest)(nil), // 11: autopilotserverrpc.RegisterSessionRequest + (*RegisterSessionResponse)(nil), // 12: autopilotserverrpc.RegisterSessionResponse + (*RevokeSessionRequest)(nil), // 13: autopilotserverrpc.RevokeSessionRequest + (*RevokeSessionResponse)(nil), // 14: autopilotserverrpc.RevokeSessionResponse + nil, // 15: autopilotserverrpc.ListFeaturesResponse.FeaturesEntry + nil, // 16: autopilotserverrpc.Feature.RulesEntry + nil, // 17: autopilotserverrpc.RegisterSessionRequest.FeatureConfigsEntry +} +var file_autopilotserver_proto_depIdxs = []int32{ + 2, // 0: autopilotserverrpc.TermsResponse.min_required_version:type_name -> autopilotserverrpc.Version + 15, // 1: autopilotserverrpc.ListFeaturesResponse.features:type_name -> autopilotserverrpc.ListFeaturesResponse.FeaturesEntry + 16, // 2: autopilotserverrpc.Feature.rules:type_name -> autopilotserverrpc.Feature.RulesEntry + 9, // 3: autopilotserverrpc.Feature.permissions_list:type_name -> autopilotserverrpc.Permissions + 10, // 4: autopilotserverrpc.Permissions.operations:type_name -> autopilotserverrpc.Operation + 17, // 5: autopilotserverrpc.RegisterSessionRequest.feature_configs:type_name -> autopilotserverrpc.RegisterSessionRequest.FeatureConfigsEntry + 2, // 6: autopilotserverrpc.RegisterSessionRequest.lit_version:type_name -> autopilotserverrpc.Version + 2, // 7: autopilotserverrpc.RegisterSessionRequest.lnd_version:type_name -> autopilotserverrpc.Version + 7, // 8: autopilotserverrpc.ListFeaturesResponse.FeaturesEntry.value:type_name -> autopilotserverrpc.Feature + 8, // 9: autopilotserverrpc.Feature.RulesEntry.value:type_name -> autopilotserverrpc.Rule + 0, // 10: autopilotserverrpc.Autopilot.Terms:input_type -> autopilotserverrpc.TermsRequest + 3, // 11: autopilotserverrpc.Autopilot.ListFeatures:input_type -> autopilotserverrpc.ListFeaturesRequest + 11, // 12: autopilotserverrpc.Autopilot.RegisterSession:input_type -> autopilotserverrpc.RegisterSessionRequest + 4, // 13: autopilotserverrpc.Autopilot.ActivateSession:input_type -> autopilotserverrpc.ActivateSessionRequest + 13, // 14: autopilotserverrpc.Autopilot.RevokeSession:input_type -> autopilotserverrpc.RevokeSessionRequest + 1, // 15: autopilotserverrpc.Autopilot.Terms:output_type -> autopilotserverrpc.TermsResponse + 6, // 16: autopilotserverrpc.Autopilot.ListFeatures:output_type -> autopilotserverrpc.ListFeaturesResponse + 12, // 17: autopilotserverrpc.Autopilot.RegisterSession:output_type -> autopilotserverrpc.RegisterSessionResponse + 5, // 18: autopilotserverrpc.Autopilot.ActivateSession:output_type -> autopilotserverrpc.ActivateSessionResponse + 14, // 19: autopilotserverrpc.Autopilot.RevokeSession:output_type -> autopilotserverrpc.RevokeSessionResponse + 15, // [15:20] is the sub-list for method output_type + 10, // [10:15] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_autopilotserver_proto_init() } +func file_autopilotserver_proto_init() { + if File_autopilotserver_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_autopilotserver_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TermsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TermsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Version); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeaturesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActivateSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActivateSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListFeaturesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Rule); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Permissions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Operation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RevokeSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_autopilotserver_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RevokeSessionResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_autopilotserver_proto_rawDesc, + NumEnums: 0, + NumMessages: 18, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_autopilotserver_proto_goTypes, + DependencyIndexes: file_autopilotserver_proto_depIdxs, + MessageInfos: file_autopilotserver_proto_msgTypes, + }.Build() + File_autopilotserver_proto = out.File + file_autopilotserver_proto_rawDesc = nil + file_autopilotserver_proto_goTypes = nil + file_autopilotserver_proto_depIdxs = nil +} diff --git a/autopilotserverrpc/autopilotserver.proto b/autopilotserverrpc/autopilotserver.proto new file mode 100644 index 000000000..8bb8bce67 --- /dev/null +++ b/autopilotserverrpc/autopilotserver.proto @@ -0,0 +1,191 @@ +syntax = "proto3"; + +package autopilotserverrpc; + +option go_package = "github.com/lightninglabs/lightning-terminal/autopilotserverrpc"; + +service Autopilot { + rpc Terms (TermsRequest) returns (TermsResponse); + rpc ListFeatures (ListFeaturesRequest) returns (ListFeaturesResponse); + rpc RegisterSession (RegisterSessionRequest) + returns (RegisterSessionResponse); + rpc ActivateSession (ActivateSessionRequest) + returns (ActivateSessionResponse); + rpc RevokeSession (RevokeSessionRequest) returns (RevokeSessionResponse); +} + +message TermsRequest { +} + +message TermsResponse { + Version min_required_version = 1; +} + +message Version { + /* + The major application version. + */ + uint32 major = 1; + + /* + The minor application version. + */ + uint32 minor = 2; + + /* + The application patch number. + */ + uint32 patch = 3; +} + +message ListFeaturesRequest { +} + +message ActivateSessionRequest { + /* + The static public key of the client that is to be used for future noise + handshakes. Since the autopilot is the initiator of a connection, the + client's key is called the "responder" key. + */ + bytes responder_pub_key = 1; +} + +message ActivateSessionResponse { + /* + The autopilot's static pub key to be used for the noise connection with the + client. + */ + bytes initiator_pub_key = 1; +} + +message ListFeaturesResponse { + /* + A map of feature name to Feature object. This map represents each of the + features supported by the autopilot server along with the details of + each feature. + */ + map features = 1; +} + +message Feature { + /* + The string identifier of the feature. + */ + string name = 1; + + /* + A human-readable description of what the feature offers. + */ + string description = 2; + + /* + A map of the rule names to rule values that make sense for this feature. + */ + map rules = 3; + + /* + A list of the permissions that the feature will require to operate. + */ + repeated Permissions permissions_list = 4; +} + +message Rule { + /* + The name of the rule. + */ + string name = 1; + + /* + The recommended default values for the rule. These may vary from feature + to feature. + */ + bytes default = 2; + + /* + The minimum sane value of the rule that is allowed for the associated + feature. + */ + bytes min_value = 3; + + /* + The maximum sane value of the rule that is allowed for the associated + feature. + */ + bytes max_value = 4; +} + +message Permissions { + /* + The URI of the method that the operation set is referring to. + */ + string method = 1; + + /* + A list of operations that the method can perform + */ + repeated Operation operations = 2; +} + +message Operation { + /* + The entity to which the action applies. + */ + string entity = 1; + + /* + The action that can be performed on the above specified entity. + */ + string action = 2; +} + +message RegisterSessionRequest { + /* + The static public key of the client that is to be used for future noise + handshakes. Since the autopilot is the initiator of a connection, the + client's key is called the "responder" key. + */ + bytes responder_pub_key = 1; + + /* + The address of the mailbox that the client will use for the LNC connection. + */ + string mailbox_addr = 2; + + /* + Set to true if tls should be skipped for when connecting to the mailbox. + */ + bool dev_server = 3; + + /* + A map from feature name to configuration bytes for that feature. + */ + map feature_configs = 4; + + /* + The version of the Lit client. + */ + Version lit_version = 5; + + /* + The version of the LND that the Lit client is using. + */ + Version lnd_version = 6; +} + +message RegisterSessionResponse { + /* + The autopilot's static pub key to be used for the noise connection with the + client. + */ + bytes initiator_pub_key = 1; +} + +message RevokeSessionRequest { + /* + The client's key for the session that the client is revoking. + */ + bytes responder_pub_key = 1; +} + +message RevokeSessionResponse { +} diff --git a/autopilotserverrpc/autopilotserver_grpc.pb.go b/autopilotserverrpc/autopilotserver_grpc.pb.go new file mode 100644 index 000000000..dd228b964 --- /dev/null +++ b/autopilotserverrpc/autopilotserver_grpc.pb.go @@ -0,0 +1,245 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package autopilotserverrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AutopilotClient is the client API for Autopilot service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AutopilotClient interface { + Terms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error) + ListFeatures(ctx context.Context, in *ListFeaturesRequest, opts ...grpc.CallOption) (*ListFeaturesResponse, error) + RegisterSession(ctx context.Context, in *RegisterSessionRequest, opts ...grpc.CallOption) (*RegisterSessionResponse, error) + ActivateSession(ctx context.Context, in *ActivateSessionRequest, opts ...grpc.CallOption) (*ActivateSessionResponse, error) + RevokeSession(ctx context.Context, in *RevokeSessionRequest, opts ...grpc.CallOption) (*RevokeSessionResponse, error) +} + +type autopilotClient struct { + cc grpc.ClientConnInterface +} + +func NewAutopilotClient(cc grpc.ClientConnInterface) AutopilotClient { + return &autopilotClient{cc} +} + +func (c *autopilotClient) Terms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*TermsResponse, error) { + out := new(TermsResponse) + err := c.cc.Invoke(ctx, "/autopilotserverrpc.Autopilot/Terms", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) ListFeatures(ctx context.Context, in *ListFeaturesRequest, opts ...grpc.CallOption) (*ListFeaturesResponse, error) { + out := new(ListFeaturesResponse) + err := c.cc.Invoke(ctx, "/autopilotserverrpc.Autopilot/ListFeatures", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) RegisterSession(ctx context.Context, in *RegisterSessionRequest, opts ...grpc.CallOption) (*RegisterSessionResponse, error) { + out := new(RegisterSessionResponse) + err := c.cc.Invoke(ctx, "/autopilotserverrpc.Autopilot/RegisterSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) ActivateSession(ctx context.Context, in *ActivateSessionRequest, opts ...grpc.CallOption) (*ActivateSessionResponse, error) { + out := new(ActivateSessionResponse) + err := c.cc.Invoke(ctx, "/autopilotserverrpc.Autopilot/ActivateSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *autopilotClient) RevokeSession(ctx context.Context, in *RevokeSessionRequest, opts ...grpc.CallOption) (*RevokeSessionResponse, error) { + out := new(RevokeSessionResponse) + err := c.cc.Invoke(ctx, "/autopilotserverrpc.Autopilot/RevokeSession", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AutopilotServer is the server API for Autopilot service. +// All implementations must embed UnimplementedAutopilotServer +// for forward compatibility +type AutopilotServer interface { + Terms(context.Context, *TermsRequest) (*TermsResponse, error) + ListFeatures(context.Context, *ListFeaturesRequest) (*ListFeaturesResponse, error) + RegisterSession(context.Context, *RegisterSessionRequest) (*RegisterSessionResponse, error) + ActivateSession(context.Context, *ActivateSessionRequest) (*ActivateSessionResponse, error) + RevokeSession(context.Context, *RevokeSessionRequest) (*RevokeSessionResponse, error) + mustEmbedUnimplementedAutopilotServer() +} + +// UnimplementedAutopilotServer must be embedded to have forward compatible implementations. +type UnimplementedAutopilotServer struct { +} + +func (UnimplementedAutopilotServer) Terms(context.Context, *TermsRequest) (*TermsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Terms not implemented") +} +func (UnimplementedAutopilotServer) ListFeatures(context.Context, *ListFeaturesRequest) (*ListFeaturesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListFeatures not implemented") +} +func (UnimplementedAutopilotServer) RegisterSession(context.Context, *RegisterSessionRequest) (*RegisterSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterSession not implemented") +} +func (UnimplementedAutopilotServer) ActivateSession(context.Context, *ActivateSessionRequest) (*ActivateSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ActivateSession not implemented") +} +func (UnimplementedAutopilotServer) RevokeSession(context.Context, *RevokeSessionRequest) (*RevokeSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevokeSession not implemented") +} +func (UnimplementedAutopilotServer) mustEmbedUnimplementedAutopilotServer() {} + +// UnsafeAutopilotServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AutopilotServer will +// result in compilation errors. +type UnsafeAutopilotServer interface { + mustEmbedUnimplementedAutopilotServer() +} + +func RegisterAutopilotServer(s grpc.ServiceRegistrar, srv AutopilotServer) { + s.RegisterService(&Autopilot_ServiceDesc, srv) +} + +func _Autopilot_Terms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TermsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).Terms(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/autopilotserverrpc.Autopilot/Terms", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).Terms(ctx, req.(*TermsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_ListFeatures_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListFeaturesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).ListFeatures(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/autopilotserverrpc.Autopilot/ListFeatures", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).ListFeatures(ctx, req.(*ListFeaturesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_RegisterSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).RegisterSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/autopilotserverrpc.Autopilot/RegisterSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).RegisterSession(ctx, req.(*RegisterSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_ActivateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ActivateSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).ActivateSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/autopilotserverrpc.Autopilot/ActivateSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).ActivateSession(ctx, req.(*ActivateSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Autopilot_RevokeSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AutopilotServer).RevokeSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/autopilotserverrpc.Autopilot/RevokeSession", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AutopilotServer).RevokeSession(ctx, req.(*RevokeSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Autopilot_ServiceDesc is the grpc.ServiceDesc for Autopilot service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Autopilot_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "autopilotserverrpc.Autopilot", + HandlerType: (*AutopilotServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Terms", + Handler: _Autopilot_Terms_Handler, + }, + { + MethodName: "ListFeatures", + Handler: _Autopilot_ListFeatures_Handler, + }, + { + MethodName: "RegisterSession", + Handler: _Autopilot_RegisterSession_Handler, + }, + { + MethodName: "ActivateSession", + Handler: _Autopilot_ActivateSession_Handler, + }, + { + MethodName: "RevokeSession", + Handler: _Autopilot_RevokeSession_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "autopilotserver.proto", +} diff --git a/autopilotserverrpc/go.mod b/autopilotserverrpc/go.mod new file mode 100644 index 000000000..577686c98 --- /dev/null +++ b/autopilotserverrpc/go.mod @@ -0,0 +1,16 @@ +module github.com/lightninglabs/lightning-terminal/autopilotserverrpc + +go 1.18 + +require ( + google.golang.org/grpc v1.39.0 + google.golang.org/protobuf v1.28.1 +) + +require ( + github.com/golang/protobuf v1.5.2 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect +) diff --git a/autopilotserverrpc/go.sum b/autopilotserverrpc/go.sum new file mode 100644 index 000000000..29d043f81 --- /dev/null +++ b/autopilotserverrpc/go.sum @@ -0,0 +1,120 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/litrpc/gen_protos.sh b/litrpc/gen_protos.sh index d6c6439d1..d0de7d16e 100755 --- a/litrpc/gen_protos.sh +++ b/litrpc/gen_protos.sh @@ -38,3 +38,8 @@ pushd litrpc format generate popd + +pushd autopilotserverrpc +format +generate no-wasm +popd \ No newline at end of file From 160977d66820fe2a3bd68d361fe453ff5a7ff313 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:20:31 +0200 Subject: [PATCH 25/47] autopilotserver: add an autopilot server client Add a client for the autopilot server along with a mock server that can be used in tests. --- autopilotserver/client.go | 640 +++++++++++++++++++++++ autopilotserver/client_test.go | 87 +++ autopilotserver/interface.go | 80 +++ autopilotserver/log.go | 25 + autopilotserver/mock/log.go | 25 + autopilotserver/mock/server.go | 394 ++++++++++++++ autopilotserverrpc/autopilotserver.pb.go | 2 +- go.mod | 9 +- go.sum | 6 +- litrpc/firewall.pb.go | 2 +- litrpc/lit-autopilot.pb.go | 2 +- litrpc/lit-sessions.pb.go | 2 +- log.go | 5 + 13 files changed, 1270 insertions(+), 9 deletions(-) create mode 100644 autopilotserver/client.go create mode 100644 autopilotserver/client_test.go create mode 100644 autopilotserver/interface.go create mode 100644 autopilotserver/log.go create mode 100644 autopilotserver/mock/log.go create mode 100644 autopilotserver/mock/server.go diff --git a/autopilotserver/client.go b/autopilotserver/client.go new file mode 100644 index 000000000..599c0466a --- /dev/null +++ b/autopilotserver/client.go @@ -0,0 +1,640 @@ +package autopilotserver + +import ( + "context" + "crypto/tls" + "encoding/hex" + "errors" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lightning-terminal/autopilotserverrpc" + "github.com/lightningnetwork/lnd/tor" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// ErrVersionIncompatible is returned when the minimum Lit version required by +// the autopilot server exceeds the version of this binary. +var ErrVersionIncompatible = fmt.Errorf("litd version is not compatible " + + "with the minimum version required by the autopilot server") + +// Config holds the configuration options for the autopilot server client. +type Config struct { + // Disable will disable the autopilot client. + Disable bool `long:"disable" description:"disable the autopilot client"` + + // Address is the domain:port of the autopilot server. + Address string `long:"address" description:"autopilot server address host:port"` + + // Proxy is the SOCKS proxy that should be used to establish the + // connection. + Proxy string `long:"proxy" description:"The host:port of a SOCKS proxy through which all connections to the autopilot server will be established over"` + + // Insecure signals that no TLS should be used if set to true. + Insecure bool `long:"insecure" description:"disable tls"` + + // TLSPath is the path to a local file that holds the autopilot + // server's TLS certificate. This is only needed if the server is using + // a self-signed cert. + TLSPath string `long:"tlspath" description:"Path to autopilot server tls certificate"` + + // PingCadence determines how often the Autopilot client should + // re-register an existing session with the Autopilot server to ensure + // that the Autopilot server knows that the session is active. + PingCadence time.Duration `long:"pingcadence" description:"How often the client should ensure that registered Autopilot sessions are active"` + + // DialOpts is a list of additional options that should be used when + // dialing the gRPC connection. + DialOpts []grpc.DialOption + + // LitVersion is the version of the Lit binary. + LitVersion Version + + // LndVersion is the version of the connected LND binary. + LndVersion Version +} + +// Version defines a software version. +type Version struct { + // Major is the major version of the software. + Major uint32 + + // Minor is the major version of the software. + Minor uint32 + + // Patch is the patch number that the software is running. + Patch uint32 +} + +// A compile-time check to ensure that the Client struct implements the +// Autopilot interface. +var _ Autopilot = (*Client)(nil) + +// Client is a client connection to the autopilot server. +type Client struct { + start sync.Once + stop sync.Once + + cfg *Config + + sessions map[string]*session + sessionsMu sync.Mutex + + featurePerms *featurePerms + + quit chan struct{} + wg sync.WaitGroup +} + +type session struct { + key *btcec.PublicKey + lastSuccess time.Time +} + +type featurePerms struct { + perms map[string]map[string]bool + lastUpdated time.Time + sync.Mutex +} + +// NewClient returns a autopilot-server client. +func NewClient(cfg *Config) (Autopilot, error) { + var err error + cfg.DialOpts, err = getAutopilotServerDialOpts( + cfg.Insecure, cfg.Proxy, cfg.TLSPath, cfg.DialOpts..., + ) + if err != nil { + return nil, err + } + + return &Client{ + cfg: cfg, + sessions: make(map[string]*session), + quit: make(chan struct{}), + featurePerms: &featurePerms{ + perms: make(map[string]map[string]bool), + }, + }, nil +} + +// Start kicks off all the goroutines required by the Client. +func (c *Client) Start(opts ...func(cfg *Config)) error { + var startErr error + c.start.Do(func() { + log.Infof("Starting Autopilot Client") + + for _, o := range opts { + o(c.cfg) + } + + version, err := c.getMinVersion(context.Background()) + if err != nil { + startErr = err + return + } + + err = c.checkCompatibility(version) + if err != nil { + startErr = err + if !errors.Is(err, ErrVersionIncompatible) { + return + } + + startErr = fmt.Errorf("lit must be on at least "+ + "version v%d.%d.%d to be compatile with the "+ + "autopilot server", version.Major, + version.Minor, version.Patch) + return + } + + c.wg.Add(2) + go c.activateSessionsForever() + go c.updateFeaturePermsForever() + }) + + return startErr +} + +// Stop cleans up any resources or goroutines managed by the Client. +func (c *Client) Stop() { + c.stop.Do(func() { + close(c.quit) + c.wg.Wait() + }) +} + +// ListFeaturePerms returns contents of the in-memory feature permissions list +// if it has been populated. +func (c *Client) ListFeaturePerms(_ context.Context) ( + map[string]map[string]bool, error) { + + c.featurePerms.Lock() + defer c.featurePerms.Unlock() + + if c.featurePerms.lastUpdated.IsZero() { + return nil, fmt.Errorf("feature permissions list is not yet " + + "populated") + } + + return c.featurePerms.perms, nil +} + +// SessionRevoked removes a session from the list of active sessions managed by +// the client. +// +// Note: this is part of the Autopilot interface. +func (c *Client) SessionRevoked(ctx context.Context, pubKey *btcec.PublicKey) { + key := hex.EncodeToString(pubKey.SerializeCompressed()) + + c.sessionsMu.Lock() + delete(c.sessions, key) + c.sessionsMu.Unlock() + + // Do a best-effort call to the Autopilot server to notify it that the + // session is being revoked. It is not the end of the world if this call + // fails since the Autopilot will move the session to inactive itself + // after a few unsuccessful connection attempts. + client, cleanup, err := c.getClientConn() + if err != nil { + log.Errorf("could not get client connection: %v", err) + return + } + defer cleanup() + + _, err = client.RevokeSession( + ctx, &autopilotserverrpc.RevokeSessionRequest{ + ResponderPubKey: pubKey.SerializeCompressed(), + }, + ) + if err != nil { + log.Errorf("could not revoke session %x: %v", + pubKey.SerializeCompressed(), err) + + return + } +} + +// activateSessionsForever periodically ensures that each of our active +// autopilot sessions are known by the autopilot to be active. +func (c *Client) activateSessionsForever() { + defer c.wg.Done() + + ctx := context.Background() + ticker := time.NewTicker(c.cfg.PingCadence) + defer ticker.Stop() + + for { + c.sessionsMu.Lock() + for _, s := range c.sessions { + // If the session was recently registered with the + // autopilot server, then we don't need to register it + // again so soon. + if !s.lastSuccess.IsZero() && + time.Since(s.lastSuccess) < c.cfg.PingCadence { + + continue + } + + key := hex.EncodeToString(s.key.SerializeCompressed()) + + log.Debugf("ensuring activation of session %s", key) + + perm, err := c.activateSession(ctx, s.key) + if err != nil { + log.Errorf("could not activate session %s: %v", + key, err) + + if perm { + delete(c.sessions, key) + } + + continue + } + + s.lastSuccess = time.Now() + } + c.sessionsMu.Unlock() + + select { + case <-ticker.C: + case <-c.quit: + return + } + } +} + +// updateFeaturePermsForever periodically attempts to update the in-memory +// feature permissions list. +// +// NOTE: this MUST be called in a goroutine. +func (c *Client) updateFeaturePermsForever() { + defer c.wg.Done() + + ctx := context.Background() + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + const ( + retryTime = time.Minute + refreshTime = time.Hour * 24 + ) + + for { + select { + case <-ticker.C: + case <-c.quit: + return + } + + c.featurePerms.Lock() + sinceUpdate := time.Since(c.featurePerms.lastUpdated) + c.featurePerms.Unlock() + + if sinceUpdate < refreshTime { + ticker.Reset(refreshTime - sinceUpdate) + continue + } + + features, err := c.ListFeatures(ctx) + if err != nil { + log.Errorf("could not fetch features from "+ + "autopilot server: %v", err) + + ticker.Reset(retryTime) + continue + } + + c.updateFeaturePerms(features) + ticker.Reset(refreshTime) + } +} + +// updateFeaturePerms updates our in-memory store of the permissions required +// for each feature. +func (c *Client) updateFeaturePerms(features map[string]*Feature) { + perms := make(map[string]map[string]bool) + for name, feature := range features { + perms[name] = make(map[string]bool) + for p := range feature.Permissions { + perms[name][p] = true + } + } + + c.featurePerms.Lock() + c.featurePerms.perms = perms + c.featurePerms.lastUpdated = time.Now() + c.featurePerms.Unlock() +} + +// ListFeatures queries the autopilot server for all the features it has +// available. +// +// Note: this is part of the Autopilot interface. +func (c *Client) ListFeatures(ctx context.Context) (map[string]*Feature, + error) { + + client, cleanup, err := c.getClientConn() + if err != nil { + return nil, err + } + defer cleanup() + + resp, err := client.ListFeatures( + ctx, &autopilotserverrpc.ListFeaturesRequest{}, + ) + if err != nil { + return nil, err + } + + features := make(map[string]*Feature, len(resp.Features)) + for i, feature := range resp.Features { + perms := unmarshalPermissions(feature.PermissionsList) + rules := unmarshalRules(feature.Rules) + features[i] = &Feature{ + Name: feature.Name, + Description: feature.Description, + Permissions: perms, + Rules: rules, + } + } + + // Take this opportunity to also update the feature permissions map. + c.updateFeaturePerms(features) + + return features, nil +} + +// RegisterSession attempts to register a session with the autopilot server. +// If the registration is successful, then the Client will also track the +// session so that it can continuously ensure that the session remains active. +// +// Note: this is part of the Autopilot interface. +func (c *Client) RegisterSession(ctx context.Context, pubKey *btcec.PublicKey, + mailboxAddr string, devServer bool, featureConf map[string][]byte) ( + *btcec.PublicKey, error) { + + remotePub, err := c.registerSession( + ctx, pubKey, mailboxAddr, devServer, featureConf, + ) + if err != nil { + log.Errorf("unsuccessful registration of session %x", + pubKey.SerializeCompressed()) + + return nil, err + } + + c.trackClient(pubKey) + + return remotePub, nil +} + +// ActivateSession attempts to inform the autopilot server that the given +// session is still active. It also adds the session to the list tracked by +// the client so that the Client can ensure that the session remains active on +// the autopilot side. +// +// Note: this is part of the Autopilot interface. +func (c *Client) ActivateSession(ctx context.Context, pubKey *btcec.PublicKey) ( + bool, error) { + + c.trackClient(pubKey) + + return c.activateSession(ctx, pubKey) +} + +// trackClient adds the given session key to the set of sessions tracked by the +// Client. +func (c *Client) trackClient(pubKey *btcec.PublicKey) { + c.sessionsMu.Lock() + defer c.sessionsMu.Unlock() + + key := hex.EncodeToString(pubKey.SerializeCompressed()) + + c.sessions[key] = &session{ + key: pubKey, + lastSuccess: time.Now(), + } +} + +// registerSession attempts to register a session with the given local static +// public key with the autopilot server. +func (c *Client) registerSession(ctx context.Context, pubKey *btcec.PublicKey, + mailboxAddr string, devServer bool, + featureConfig map[string][]byte) (*btcec.PublicKey, error) { + + client, cleanup, err := c.getClientConn() + if err != nil { + return nil, err + } + defer cleanup() + + resp, err := client.RegisterSession( + ctx, &autopilotserverrpc.RegisterSessionRequest{ + ResponderPubKey: pubKey.SerializeCompressed(), + MailboxAddr: mailboxAddr, + DevServer: devServer, + FeatureConfigs: featureConfig, + LitVersion: marshalVersion(c.cfg.LitVersion), + LndVersion: marshalVersion(c.cfg.LndVersion), + }, + ) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(resp.InitiatorPubKey) +} + +func (c *Client) getMinVersion(ctx context.Context) (*Version, error) { + client, cleanup, err := c.getClientConn() + if err != nil { + return nil, err + } + defer cleanup() + + terms, err := client.Terms(ctx, &autopilotserverrpc.TermsRequest{}) + if err != nil { + return nil, err + } + + return &Version{ + Major: terms.MinRequiredVersion.Major, + Minor: terms.MinRequiredVersion.Minor, + Patch: terms.MinRequiredVersion.Patch, + }, nil +} + +func (c *Client) checkCompatibility(minV *Version) error { + v := c.cfg.LitVersion + + if v.Major != minV.Major { + if v.Major > minV.Major { + return nil + } + return ErrVersionIncompatible + } + + if v.Minor != minV.Minor { + if v.Minor > minV.Minor { + return nil + } + return ErrVersionIncompatible + } + + if v.Patch != minV.Patch { + if v.Patch > minV.Patch { + return nil + } + return ErrVersionIncompatible + } + + // The actual version and expected version are identical. + return nil +} + +// activateSession ensures that the autopilot server sees the given session as +// active. The returned boolean is true if the error is permanent and the +// session should be revoked. If it is false, then the client should retry +// again in the future. +func (c *Client) activateSession(ctx context.Context, pubKey *btcec.PublicKey) ( + bool, error) { + + client, cleanup, err := c.getClientConn() + if err != nil { + return false, err + } + defer cleanup() + + _, err = client.ActivateSession( + ctx, &autopilotserverrpc.ActivateSessionRequest{ + ResponderPubKey: pubKey.SerializeCompressed(), + }, + ) + if err == nil { + return false, nil + } + + // TODO(elle): use structured GRPC errors instead. + if strings.Contains(err.Error(), "the client has been rejected") { + return true, err + } + + return false, err +} + +// getClientConn creates a connection to the autopilot server and returns this +// connection along with a cleanup function to be used when the connection +// is no longer needed. +func (c *Client) getClientConn() (autopilotserverrpc.AutopilotClient, + func(), error) { + + serverConn, err := grpc.Dial(c.cfg.Address, c.cfg.DialOpts...) + if err != nil { + return nil, nil, fmt.Errorf("unable to connect to RPC "+ + "server: %v", err) + } + + clientConn := autopilotserverrpc.NewAutopilotClient(serverConn) + + return clientConn, func() { + err = serverConn.Close() + if err != nil { + log.Errorf("could not close server conn: %v", err) + } + }, nil +} + +// getAutopilotServerDialOpts returns the dial options to connect to the +// autopilot server. +func getAutopilotServerDialOpts(insecure bool, proxyAddress, tlsPath string, + dialOpts ...grpc.DialOption) ([]grpc.DialOption, error) { + + // Create a copy of the dial options array. + opts := dialOpts + + // There are three options to connect to an autopilot server, either + // insecure, using a self-signed certificate or with a certificate + // signed by a public CA. + switch { + case insecure: + opts = append(opts, grpc.WithInsecure()) + + case tlsPath != "": + // Load the specified TLS certificate and build + // transport credentials + creds, err := credentials.NewClientTLSFromFile(tlsPath, "") + if err != nil { + return nil, err + } + opts = append(opts, grpc.WithTransportCredentials(creds)) + + default: + creds := credentials.NewTLS(&tls.Config{}) + opts = append(opts, grpc.WithTransportCredentials(creds)) + } + // If a SOCKS proxy address was specified, + // then we should dial through it. + if proxyAddress != "" { + log.Infof("Proxying connection to autopilotserver server "+ + "over Tor SOCKS proxy %v", proxyAddress) + torDialer := func(_ context.Context, addr string) (net.Conn, + error) { + + return tor.Dial( + addr, proxyAddress, false, false, + tor.DefaultConnTimeout, + ) + } + opts = append(opts, grpc.WithContextDialer(torDialer)) + } + + return opts, nil +} + +func unmarshalPermissions( + perms []*autopilotserverrpc.Permissions) map[string][]bakery.Op { + + res := make(map[string][]bakery.Op) + for _, perm := range perms { + operations := make([]bakery.Op, len(perm.Operations)) + for i, op := range perm.Operations { + operations[i] = bakery.Op{ + Entity: op.Entity, + Action: op.Action, + } + } + + res[perm.Method] = operations + } + + return res +} + +func unmarshalRules( + rules map[string]*autopilotserverrpc.Rule) map[string]*RuleValues { + + res := make(map[string]*RuleValues, len(rules)) + for name, rule := range rules { + res[name] = &RuleValues{ + Default: rule.Default, + MinVal: rule.MinValue, + MaxVal: rule.MaxValue, + } + } + + return res +} + +func marshalVersion(v Version) *autopilotserverrpc.Version { + return &autopilotserverrpc.Version{ + Major: v.Major, + Minor: v.Minor, + Patch: v.Patch, + } +} diff --git a/autopilotserver/client_test.go b/autopilotserver/client_test.go new file mode 100644 index 000000000..755b3a640 --- /dev/null +++ b/autopilotserver/client_test.go @@ -0,0 +1,87 @@ +package autopilotserver + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lightning-terminal/autopilotserver/mock" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/stretchr/testify/require" +) + +// TestAutopilotClient tests that the Client correctly interacts with the +// Autopilot server. +func TestAutopilotClient(t *testing.T) { + ctx := context.Background() + + // Spin up a new mock Autopilot server. + server := mock.NewServer() + require.NoError(t, server.Start()) + t.Cleanup(server.Stop) + + // Create a new client and connect it to the mock server. We set a very + // short ping cadence so that we can test that the client correctly + // ensures re-activation of a session. + addr := fmt.Sprintf("localhost:%d", server.GetPort()) + client, err := NewClient(&Config{ + Address: addr, + Insecure: true, + PingCadence: time.Second, + }) + require.NoError(t, err) + require.NoError(t, client.Start()) + t.Cleanup(client.Stop) + + privKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + pubKey := privKey.PubKey() + + // Activating a session that the server does not yet know about should + // error. + _, err = client.ActivateSession(ctx, pubKey) + require.ErrorContains(t, err, "no such client") + + // Register the client. + _, err = client.RegisterSession(ctx, pubKey, "", false, nil) + require.NoError(t, err) + + // Assert that the server sees the new client and has it in the Active + // state. + state, err := server.GetClientState(pubKey) + require.NoError(t, err) + require.True(t, mock.ClientStateActive == state) + + // Let the server move the client to an Inactive state. + err = server.SetClientState(pubKey, mock.ClientStateInactive) + require.NoError(t, err) + state, err = server.GetClientState(pubKey) + require.NoError(t, err) + require.True(t, mock.ClientStateInactive == state) + + // Manually inform the server that the session is active. + _, err = client.ActivateSession(ctx, pubKey) + require.NoError(t, err) + + // Assert that the server moved the client to the Active state. + state, err = server.GetClientState(pubKey) + require.NoError(t, err) + require.True(t, mock.ClientStateActive == state) + + // Once again, let the server move the client to the inactive state. + err = server.SetClientState(pubKey, mock.ClientStateInactive) + require.NoError(t, err) + state, err = server.GetClientState(pubKey) + require.NoError(t, err) + require.True(t, mock.ClientStateInactive == state) + + // Now wait for client to re-activate the session with the server + err = wait.Predicate(func() bool { + state, err = server.GetClientState(pubKey) + require.NoError(t, err) + return state == mock.ClientStateActive + }, time.Second*5) + require.NoError(t, err) +} diff --git a/autopilotserver/interface.go b/autopilotserver/interface.go new file mode 100644 index 000000000..3c0f4bcbe --- /dev/null +++ b/autopilotserver/interface.go @@ -0,0 +1,80 @@ +package autopilotserver + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// Autopilot represents the functionality exposed by an autopilot server. +type Autopilot interface { + // ListFeatures fetches the set of features offered by the autopilot + // server along with all the rules and permissions required for those + // features. + ListFeatures(ctx context.Context) (map[string]*Feature, error) + + // ListFeaturePerms returns a map of feature names to a map of + // permissions required for each feature. This call uses an in-memory + // store that is updated periodically and so this should be used instead + // of the ListFeatures call if only the permissions are required to + // avoid doing multiple calls to the autopilot server. The ListFeatures + // call can however be used to force the update of the in-memory list. + ListFeaturePerms(ctx context.Context) (map[string]map[string]bool, + error) + + // RegisterSession attempts to register a session with the autopilot + // server. If the registration is successful, then the Client will also + // track the session so that it can continuously ensure that the session + // remains active. + RegisterSession(ctx context.Context, pubKey *btcec.PublicKey, + mailboxAddr string, devServer bool, + featureConf map[string][]byte) (*btcec.PublicKey, error) + + // ActivateSession attempts to inform the autopilot server that the + // given session is still active. After this is called, the autopilot + // client will periodically ensure that the session remains active. + // The boolean returned is true if the error received was permanent + // meaning that the session should be revoked and recreated. + ActivateSession(ctx context.Context, pubKey *btcec.PublicKey) (bool, + error) + + // SessionRevoked should be called when a session is no longer active + // so that the client can forget the session. + SessionRevoked(ctx context.Context, key *btcec.PublicKey) + + // Start kicks off the goroutines of the client. + Start(opts ...func(cfg *Config)) error + + // Stop cleans up any resources held by the client. + Stop() +} + +// Feature holds all the info necessary to subscribe to a feature offered by +// the autopilot server. +type Feature struct { + // Name is the name of the feature. + Name string + + // Description is a human-readable description of what the feature + // offers + Description string + + // Permissions is a list of RPC methods and access writes a feature + // will need. + Permissions map[string][]bakery.Op + + // Rules is a list of all the firewall that must be specified for this + // feature. + Rules map[string]*RuleValues +} + +// RuleValues holds the default value along with the sane max and min values +// that the autopilot server indicates makes sense for feature that the rule is +// being applied to. The values can be unmarshalled in a higher layer if the +// name of the rule is known to LiT. +type RuleValues struct { + Default []byte + MinVal []byte + MaxVal []byte +} diff --git a/autopilotserver/log.go b/autopilotserver/log.go new file mode 100644 index 000000000..ef9c49f13 --- /dev/null +++ b/autopilotserver/log.go @@ -0,0 +1,25 @@ +package autopilotserver + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "AUTO" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/autopilotserver/mock/log.go b/autopilotserver/mock/log.go new file mode 100644 index 000000000..00816644b --- /dev/null +++ b/autopilotserver/mock/log.go @@ -0,0 +1,25 @@ +package mock + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +const Subsystem = "AUTO" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/autopilotserver/mock/server.go b/autopilotserver/mock/server.go new file mode 100644 index 000000000..de9d0f1a8 --- /dev/null +++ b/autopilotserver/mock/server.go @@ -0,0 +1,394 @@ +package mock + +import ( + "context" + "encoding/hex" + "fmt" + "net" + "sync" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lightning-terminal/autopilotserverrpc" + "github.com/lightninglabs/lightning-terminal/rules" + "github.com/lightningnetwork/lnd/lntest" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// Server implements the autopilotserverrpc.AutopilotServer interface and is +// used to mock the autopilot server in tests. +type Server struct { + autopilotserverrpc.AutopilotServer + + // featureSet is the set of features that the autopilot server supports. + featureSet map[string]*Feature + + // sessions is a map from the public key string of the remote node + // (the litd node in this case) to the private key of the autopilot + // server. + sessions map[string]*clientSession + sessMu sync.Mutex + + // port is the port number on which the mock autopilot server will + // host its grpc service. + port int + + grpcServer *grpc.Server + + wg sync.WaitGroup +} + +type ClientState uint8 + +const ( + ClientStateActive = iota + ClientStateInactive +) + +type clientSession struct { + key *btcec.PrivateKey + state ClientState +} + +// NewServer constructs a new MockAutoPilotServer. +func NewServer() *Server { + return &Server{ + port: lntest.NextAvailablePort(), + sessions: make(map[string]*clientSession), + grpcServer: grpc.NewServer( + grpc.Creds(insecure.NewCredentials()), + ), + featureSet: defaultFeatures, + } +} + +// Start kicks off the mock autopilot grpc server. +func (m *Server) Start() error { + lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", m.port)) + if err != nil { + return err + } + + autopilotserverrpc.RegisterAutopilotServer(m.grpcServer, m) + + m.wg.Add(1) + go func() { + defer m.wg.Done() + + err := m.grpcServer.Serve(lis) + if err != nil && err != grpc.ErrServerStopped { + log.Errorf("RPC server stopped with error: %v", err) + } + }() + + return nil +} + +// Stop cleans up any resources held by the mock server. +func (m *Server) Stop() { + m.grpcServer.Stop() + m.wg.Wait() +} + +// GetPort returns the port number that the mock server is serving its grpc +// server on. +func (m *Server) GetPort() int { + return m.port +} + +// SetFeatures can be used to override the feature set served by the mock +// autopilot server. +func (m *Server) SetFeatures(f map[string]*Feature) { + m.featureSet = f +} + +// Terms returns any meta data from the autopilot server. +// +// Note: this is part of the autopilotrpc.AutopilotServer interface. +func (m *Server) Terms(context.Context, *autopilotserverrpc.TermsRequest) ( + *autopilotserverrpc.TermsResponse, error) { + + return &autopilotserverrpc.TermsResponse{ + MinRequiredVersion: &autopilotserverrpc.Version{ + Major: 0, + Minor: 0, + Patch: 0, + }, + }, nil +} + +// ListFeatures converts the mockFeatures into the form that the autopilot +// server would. +// +// Note: this is part of the autopilotrpc.AutopilotServer interface. +func (m *Server) ListFeatures(_ context.Context, + _ *autopilotserverrpc.ListFeaturesRequest) ( + *autopilotserverrpc.ListFeaturesResponse, error) { + + res := make(map[string]*autopilotserverrpc.Feature, len(m.featureSet)) + for name, f := range m.featureSet { + rules, err := rulesToRPC(f.Rules) + if err != nil { + return nil, err + } + + res[name] = &autopilotserverrpc.Feature{ + Name: name, + Description: f.Description, + Rules: rules, + PermissionsList: permissionsToRPC(f.Permissions), + } + } + + return &autopilotserverrpc.ListFeaturesResponse{ + Features: res, + }, nil +} + +// RegisterSession will create a new priv key for the autopilot server and +// return the corresponding public key. +// +// Note: this is part of the autopilotrpc.AutopilotServer interface. +func (m *Server) RegisterSession(_ context.Context, + req *autopilotserverrpc.RegisterSessionRequest) ( + *autopilotserverrpc.RegisterSessionResponse, error) { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + _, ok := m.sessions[hex.EncodeToString(req.ResponderPubKey)] + if ok { + return nil, fmt.Errorf("client already registered") + } + + priv, err := btcec.NewPrivateKey() + if err != nil { + return nil, err + } + + m.sessions[hex.EncodeToString(req.ResponderPubKey)] = &clientSession{ + key: priv, + state: ClientStateActive, + } + + return &autopilotserverrpc.RegisterSessionResponse{ + InitiatorPubKey: priv.PubKey().SerializeCompressed(), + }, nil +} + +func (m *Server) ActivateSession(_ context.Context, + req *autopilotserverrpc.ActivateSessionRequest) ( + *autopilotserverrpc.ActivateSessionResponse, error) { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + session, ok := m.sessions[hex.EncodeToString(req.ResponderPubKey)] + if !ok { + return nil, fmt.Errorf("no such client") + } + + session.state = ClientStateActive + return &autopilotserverrpc.ActivateSessionResponse{}, nil +} + +// RevokeSession revokes a single session and also stops it if it is currently +// active. +// +// Note: this is part of the autopilotrpc.AutopilotServer interface. +func (m *Server) RevokeSession(_ context.Context, + req *autopilotserverrpc.RevokeSessionRequest) ( + *autopilotserverrpc.RevokeSessionResponse, error) { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + delete(m.sessions, hex.EncodeToString(req.ResponderPubKey)) + + return &autopilotserverrpc.RevokeSessionResponse{}, nil +} + +// GetPrivKey can be used to extract the private key that the autopilot created +// for the given litd static key. +func (m *Server) GetPrivKey(remoteKey *btcec.PublicKey) ( + *btcec.PrivateKey, error) { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + key := hex.EncodeToString(remoteKey.SerializeCompressed()) + session, ok := m.sessions[key] + if !ok { + return nil, fmt.Errorf("no key found") + } + + return session.key, nil +} + +func (m *Server) GetClientState(remoteKey *btcec.PublicKey) ( + ClientState, error) { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + key := hex.EncodeToString(remoteKey.SerializeCompressed()) + session, ok := m.sessions[key] + if !ok { + return 0, fmt.Errorf("no such client found") + } + + return session.state, nil +} + +func (m *Server) SetClientState(remoteKey *btcec.PublicKey, + s ClientState) error { + + m.sessMu.Lock() + defer m.sessMu.Unlock() + + key := hex.EncodeToString(remoteKey.SerializeCompressed()) + session, ok := m.sessions[key] + if !ok { + return fmt.Errorf("no such client found") + } + + session.state = s + + return nil +} + +// Feature is a feature that the autopilot server could return. +type Feature struct { + Description string + Rules map[string]*RuleRanges + Permissions map[string][]bakery.Op +} + +// defaultFeatures is an example of a set of features that the autopilot server +// could return. +var defaultFeatures = map[string]*Feature{ + "HealthCheck": { + Description: "check that your node is up", + Rules: map[string]*RuleRanges{ + rules.RateLimitName: rateLimitRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/GetInfo": {{ + Entity: "info", + Action: "read", + }}, + }, + }, + "AutoFees": { + Description: "manages your channel fees", + Rules: map[string]*RuleRanges{ + rules.RateLimitName: rateLimitRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/ListChannels": {{ + Entity: "offchain", + Action: "read", + }}, + "/lnrpc.Lightning/UpdateChannelPolicy": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/FeeReport": {{ + Entity: "offchain", + Action: "read", + }}, + }, + }, +} + +var rateLimitRule = &RuleRanges{ + Default: &rules.RateLimit{ + WriteLimit: &rules.Rate{ + Iterations: 1, + NumHours: 1, + }, + ReadLimit: &rules.Rate{ + Iterations: 10, + NumHours: 1, + }, + }, + MinVal: &rules.RateLimit{ + WriteLimit: &rules.Rate{ + Iterations: 0, + NumHours: 1, + }, + ReadLimit: &rules.Rate{ + Iterations: 1, + NumHours: 1, + }, + }, + MaxVal: &rules.RateLimit{ + WriteLimit: &rules.Rate{ + Iterations: 10, + NumHours: 1, + }, + ReadLimit: &rules.Rate{ + Iterations: 1000, + NumHours: 1, + }, + }, +} + +type RuleRanges struct { + Default rules.Values + MinVal rules.Values + MaxVal rules.Values +} + +func rulesToRPC(rulesMap map[string]*RuleRanges) ( + map[string]*autopilotserverrpc.Rule, error) { + + res := make(map[string]*autopilotserverrpc.Rule, len(rulesMap)) + for name, rule := range rulesMap { + defaultVals, err := rules.Marshal(rule.Default) + if err != nil { + return nil, err + } + + minVals, err := rules.Marshal(rule.MinVal) + if err != nil { + return nil, err + } + + maxVals, err := rules.Marshal(rule.MaxVal) + if err != nil { + return nil, err + } + + res[name] = &autopilotserverrpc.Rule{ + Name: name, + Default: defaultVals, + MinValue: minVals, + MaxValue: maxVals, + } + } + + return res, nil +} + +func permissionsToRPC(ps map[string][]bakery.Op) []*autopilotserverrpc.Permissions { + res := make([]*autopilotserverrpc.Permissions, len(ps)) + + for method, ops := range ps { + operations := make([]*autopilotserverrpc.Operation, len(ops)) + for i, op := range ops { + operations[i] = &autopilotserverrpc.Operation{ + Entity: op.Entity, + Action: op.Action, + } + } + + res = append(res, &autopilotserverrpc.Permissions{ + Method: method, + Operations: operations, + }) + } + + return res +} diff --git a/autopilotserverrpc/autopilotserver.pb.go b/autopilotserverrpc/autopilotserver.pb.go index 2d25e2ddf..f83a72a8f 100644 --- a/autopilotserverrpc/autopilotserver.pb.go +++ b/autopilotserverrpc/autopilotserver.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.28.1 // protoc v3.6.1 // source: autopilotserver.proto diff --git a/go.mod b/go.mod index 90267ef72..fab5f875f 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/lightninglabs/aperture v0.1.18-beta github.com/lightninglabs/faraday v0.2.9-alpha github.com/lightninglabs/lightning-node-connect v0.1.12-alpha + github.com/lightninglabs/lightning-terminal/autopilotserverrpc v0.0.1 github.com/lightninglabs/lndclient v0.15.4-0 github.com/lightninglabs/loop v0.20.2-beta github.com/lightninglabs/loop/swapserverrpc v1.0.3 @@ -25,6 +26,7 @@ require ( github.com/lightningnetwork/lnd/cert v1.1.1 github.com/lightningnetwork/lnd/kvdb v1.3.1 github.com/lightningnetwork/lnd/tlv v1.0.3 + github.com/lightningnetwork/lnd/tor v1.0.1 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76 github.com/stretchr/testify v1.8.0 @@ -34,7 +36,7 @@ require ( golang.org/x/net v0.0.0-20211216030914-fe4d6282115f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.39.0 - google.golang.org/protobuf v1.27.1 + google.golang.org/protobuf v1.28.1 gopkg.in/macaroon-bakery.v2 v2.1.0 gopkg.in/macaroon.v2 v2.1.0 ) @@ -59,7 +61,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -108,7 +110,6 @@ require ( github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect github.com/lightningnetwork/lnd/queue v1.1.0 // indirect github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect - github.com/lightningnetwork/lnd/tor v1.0.1 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect @@ -173,4 +174,6 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) +replace github.com/lightninglabs/lightning-terminal/autopilotserverrpc => ./autopilotserverrpc + go 1.18 diff --git a/go.sum b/go.sum index 718442188..e05a6bdd1 100644 --- a/go.sum +++ b/go.sum @@ -168,8 +168,9 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -1237,8 +1238,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index 8a4e2d7c1..e4e9eb4f4 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.28.1 // protoc v3.6.1 // source: firewall.proto diff --git a/litrpc/lit-autopilot.pb.go b/litrpc/lit-autopilot.pb.go index 87aafe9fd..67d324634 100644 --- a/litrpc/lit-autopilot.pb.go +++ b/litrpc/lit-autopilot.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.28.1 // protoc v3.6.1 // source: lit-autopilot.proto diff --git a/litrpc/lit-sessions.pb.go b/litrpc/lit-sessions.pb.go index 57edd4b23..091e35bd6 100644 --- a/litrpc/lit-sessions.pb.go +++ b/litrpc/lit-sessions.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.28.1 // protoc v3.6.1 // source: lit-sessions.proto diff --git a/log.go b/log.go index c3b90eebf..96457a66f 100644 --- a/log.go +++ b/log.go @@ -5,6 +5,7 @@ import ( "github.com/lightninglabs/faraday" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/autopilotserver" "github.com/lightninglabs/lightning-terminal/firewall" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lightning-terminal/rules" @@ -71,6 +72,10 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) { root, firewall.Subsystem, intercept, firewall.UseLogger, ) lnd.AddSubLogger(root, rules.Subsystem, intercept, rules.UseLogger) + lnd.AddSubLogger( + root, autopilotserver.Subsystem, intercept, + autopilotserver.UseLogger, + ) // Add daemon loggers to lnd's root logger. faraday.SetupLoggers(root, intercept) From 4639e7e5ba03197ec1b57d627d207c59ff173105 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 24 Aug 2022 08:37:28 +0200 Subject: [PATCH 26/47] session+rpcserver: add feature config to session type --- session/interface.go | 13 ++++- session/tlv.go | 134 ++++++++++++++++++++++++++++++++++++++++++- session/tlv_test.go | 21 +++++-- session_rpcserver.go | 2 +- 4 files changed, 159 insertions(+), 11 deletions(-) diff --git a/session/interface.go b/session/interface.go index 4194fbb64..6f9b4bd59 100644 --- a/session/interface.go +++ b/session/interface.go @@ -40,6 +40,10 @@ type MacaroonRecipe struct { Caveats []macaroon.Caveat } +// FeaturesConfig is a map from feature name to a raw byte array which stores +// any config feature config options. +type FeaturesConfig map[string][]byte + // Session is a struct representing a long-term Terminal Connect session. type Session struct { ID ID @@ -57,6 +61,7 @@ type Session struct { LocalPrivateKey *btcec.PrivateKey LocalPublicKey *btcec.PublicKey RemotePublicKey *btcec.PublicKey + FeatureConfig *FeaturesConfig } // MacaroonBaker is a function type for baking a super macaroon. @@ -65,8 +70,8 @@ type MacaroonBaker func(ctx context.Context, rootKeyID uint64, // NewSession creates a new session with the given user-defined parameters. func NewSession(label string, typ Type, expiry time.Time, serverAddr string, - devServer bool, perms []bakery.Op, caveats []macaroon.Caveat) (*Session, - error) { + devServer bool, perms []bakery.Op, caveats []macaroon.Caveat, + featureConfig FeaturesConfig) (*Session, error) { _, pairingSecret, err := mailbox.NewPassphraseEntropy() if err != nil { @@ -106,6 +111,10 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, } } + if len(featureConfig) != 0 { + sess.FeatureConfig = &featureConfig + } + return sess, nil } diff --git a/session/tlv.go b/session/tlv.go index 9735a2685..c237f09d3 100644 --- a/session/tlv.go +++ b/session/tlv.go @@ -25,7 +25,7 @@ const ( typeRemotePublicKey tlv.Type = 11 typeMacaroonRecipe tlv.Type = 12 typeCreatedAt tlv.Type = 13 - typeReservedNum1 tlv.Type = 14 + typeFeaturesConfig tlv.Type = 14 typeReservedNum2 tlv.Type = 15 typeRevokedAt tlv.Type = 16 @@ -42,6 +42,9 @@ const ( typePermEntity tlv.Type = 1 typePermAction tlv.Type = 2 + + typeFeatureName tlv.Type = 1 + typeFeatureConfig tlv.Type = 2 ) // SerializeSession binary serializes the given session to the writer using the @@ -111,7 +114,23 @@ func SerializeSession(w io.Writer, session *Session) error { tlvRecords = append( tlvRecords, tlv.MakePrimitiveRecord(typeCreatedAt, &createdAt), - tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), + ) + + if session.FeatureConfig != nil && len(*session.FeatureConfig) != 0 { + tlvRecords = append(tlvRecords, tlv.MakeDynamicRecord( + typeFeaturesConfig, session.FeatureConfig, + func() uint64 { + return recordSize( + featureConfigEncoder, + session.FeatureConfig, + ) + }, + featureConfigEncoder, featureConfigDecoder, + )) + } + + tlvRecords = append( + tlvRecords, tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), ) tlvStream, err := tlv.NewStream(tlvRecords...) @@ -132,6 +151,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { state, typ, devServer uint8 expiry, createdAt, revokedAt uint64 macRecipe MacaroonRecipe + featureConfig FeaturesConfig ) tlvStream, err := tlv.NewStream( tlv.MakePrimitiveRecord(typeLabel, &label), @@ -153,6 +173,10 @@ func DeserializeSession(r io.Reader) (*Session, error) { macaroonRecipeEncoder, macaroonRecipeDecoder, ), tlv.MakePrimitiveRecord(typeCreatedAt, &createdAt), + tlv.MakeDynamicRecord( + typeFeaturesConfig, &featureConfig, nil, + featureConfigEncoder, featureConfigDecoder, + ), tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), ) if err != nil { @@ -191,9 +215,115 @@ func DeserializeSession(r io.Reader) (*Session, error) { ) } + if t, ok := parsedTypes[typeFeaturesConfig]; ok && t == nil { + session.FeatureConfig = &featureConfig + } + return session, nil } +func featureConfigEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*FeaturesConfig); ok { + for n, config := range *v { + name := []byte(n) + config := config + + var permsTLVBytes bytes.Buffer + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord(typeFeatureName, &name), + tlv.MakePrimitiveRecord( + typeFeatureConfig, &config, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Encode(&permsTLVBytes) + if err != nil { + return err + } + + // We encode the record with a varint length followed by + // the _raw_ TLV bytes. + tlvLen := uint64(len(permsTLVBytes.Bytes())) + if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil { + return err + } + + _, err = w.Write(permsTLVBytes.Bytes()) + if err != nil { + return err + } + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "FeaturesConfig") +} + +func featureConfigDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*FeaturesConfig); ok { + featureConfig := make(map[string][]byte) + + // Using this information, we'll create a new limited + // reader that'll return an EOF once the end has been + // reached so the stream stops consuming bytes. + innerTlvReader := io.LimitedReader{ + R: r, + N: int64(l), + } + + for { + // Read out the varint that encodes the size of this + // inner TLV record. + blobSize, err := tlv.ReadVarInt(&innerTlvReader, buf) + if err == io.EOF { + break + } else if err != nil { + return err + } + + innerInnerTlvReader := io.LimitedReader{ + R: &innerTlvReader, + N: int64(blobSize), + } + + var ( + name []byte + config []byte + ) + tlvStream, err := tlv.NewStream( + tlv.MakePrimitiveRecord( + typeFeatureName, &name, + ), + tlv.MakePrimitiveRecord( + typeFeatureConfig, &config, + ), + ) + if err != nil { + return err + } + + err = tlvStream.Decode(&innerInnerTlvReader) + if err != nil { + return err + } + + featureConfig[string(name)] = config + } + + *v = featureConfig + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "FeaturesConfig") +} + // macaroonRecipeEncoder is a custom TLV encoder for a MacaroonRecipe record. func macaroonRecipeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { if v, ok := val.(*MacaroonRecipe); ok { diff --git a/session/tlv_test.go b/session/tlv_test.go index 2809f4bb9..8c2d55390 100644 --- a/session/tlv_test.go +++ b/session/tlv_test.go @@ -51,11 +51,12 @@ var ( // and deserialized from and to the tlv binary format successfully. func TestSerializeDeserializeSession(t *testing.T) { tests := []struct { - name string - sessType Type - revokedAt time.Time - perms []bakery.Op - caveats []macaroon.Caveat + name string + sessType Type + revokedAt time.Time + perms []bakery.Op + caveats []macaroon.Caveat + featureConfig map[string][]byte }{ { name: "session 1", @@ -70,6 +71,14 @@ func TestSerializeDeserializeSession(t *testing.T) { perms: perms, caveats: caveats, }, + { + name: "session 3", + sessType: TypeMacaroonCustom, + featureConfig: map[string][]byte{ + "AutoFees": {1, 2, 3, 4}, + "AutoSomething": {4, 3, 4, 5, 6, 6}, + }, + }, } for _, test := range tests { @@ -78,7 +87,7 @@ func TestSerializeDeserializeSession(t *testing.T) { test.name, test.sessType, time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), "foo.bar.baz:1234", true, test.perms, - test.caveats, + test.caveats, test.featureConfig, ) require.NoError(t, err) diff --git a/session_rpcserver.go b/session_rpcserver.go index 1f2a69bf2..8fc8a5b01 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -249,7 +249,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, sess, err := session.NewSession( req.Label, typ, expiry, req.MailboxServerAddr, req.DevServer, - uniquePermissions, caveats, + uniquePermissions, caveats, nil, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) From 683cdbd5663a4e7eecf62818f315dc8bc102d4cd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:23:04 +0200 Subject: [PATCH 27/47] terminal: init the autopilot client --- config.go | 6 ++++++ terminal.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/config.go b/config.go index b99ab1d8b..aa482c00d 100644 --- a/config.go +++ b/config.go @@ -17,6 +17,7 @@ import ( "github.com/lightninglabs/faraday" "github.com/lightninglabs/faraday/chain" "github.com/lightninglabs/faraday/frdrpcserver" + "github.com/lightninglabs/lightning-terminal/autopilotserver" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopd" @@ -185,6 +186,8 @@ type Config struct { RPCMiddleware *mid.Config `group:"RPC middleware options" namespace:"rpcmiddleware"` + Autopilot *autopilotserver.Config `group:"Autopilot server options" namespace:"autopilot"` + // faradayRpcConfig is a subset of faraday's full configuration that is // passed into faraday's RPC server. faradayRpcConfig *frdrpcserver.Config @@ -325,6 +328,9 @@ func defaultConfig() *Config { Pool: &poolDefaultConfig, RPCMiddleware: mid.DefaultConfig(), FirstLNCConnDeadline: defaultFirstLNCConnTimeout, + Autopilot: &autopilotserver.Config{ + PingCadence: time.Hour, + }, } } diff --git a/terminal.go b/terminal.go index adfa6205b..d194cc777 100644 --- a/terminal.go +++ b/terminal.go @@ -22,6 +22,7 @@ import ( "github.com/lightninglabs/faraday/frdrpc" "github.com/lightninglabs/faraday/frdrpcserver" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/autopilotserver" "github.com/lightninglabs/lightning-terminal/firewall" "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" @@ -64,6 +65,9 @@ import ( ) const ( + MainnetServer = "autopilot.lightning.finance:12010" + TestnetServer = "test.autopilot.lightning.finance:12010" + defaultServerTimeout = 10 * time.Second defaultConnectTimeout = 15 * time.Second defaultStartupTimeout = 5 * time.Second @@ -156,6 +160,8 @@ type LightningTerminal struct { faradayServer *frdrpcserver.RPCServer faradayStarted bool + autopilotClient autopilotserver.Autopilot + ruleMgrs rules.ManagerSet loopServer *loopd.Daemon @@ -261,6 +267,35 @@ func (g *LightningTerminal) Run() error { return fmt.Errorf("error creating session DB: %v", err) } + if !g.cfg.Autopilot.Disable { + if g.cfg.Autopilot.Address == "" && + len(g.cfg.Autopilot.DialOpts) == 0 { + + switch g.cfg.Network { + case "mainnet": + g.cfg.Autopilot.Address = MainnetServer + case "testnet": + g.cfg.Autopilot.Address = TestnetServer + default: + return errors.New("no autopilot server " + + "address specified") + } + } + + g.cfg.Autopilot.LitVersion = autopilotserver.Version{ + Major: uint32(appMajor), + Minor: uint32(appMinor), + Patch: uint32(appPatch), + } + + g.autopilotClient, err = autopilotserver.NewClient( + g.cfg.Autopilot, + ) + if err != nil { + return err + } + } + g.sessionRpcServer, err = newSessionRPCServer(&sessionRpcServerConfig{ basicAuth: g.rpcProxy.basicAuth, dbDir: filepath.Join(g.cfg.LitDir, g.cfg.Network), @@ -649,6 +684,21 @@ func (g *LightningTerminal) startSubservers() error { } g.macaroonServiceStarted = true + if !g.cfg.Autopilot.Disable { + withLndVersion := func(cfg *autopilotserver.Config) { + cfg.LndVersion = autopilotserver.Version{ + Major: g.lndClient.Version.AppMajor, + Minor: g.lndClient.Version.AppMinor, + Patch: g.lndClient.Version.AppPatch, + } + } + + if err = g.autopilotClient.Start(withLndVersion); err != nil { + return fmt.Errorf("could not start the autopilot "+ + "client: %v", err) + } + } + log.Infof("Starting LiT session server") if err = g.sessionRpcServer.start(); err != nil { return err @@ -970,6 +1020,10 @@ func (g *LightningTerminal) shutdown() error { } } + if g.autopilotClient != nil { + g.autopilotClient.Stop() + } + if g.sessionRpcServerStarted { if err := g.sessionRpcServer.stop(); err != nil { log.Errorf("Error closing session DB: %v", err) From 5255e952d823049bf2bf173119931a7055aecb47 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 8 Sep 2022 12:27:59 +0200 Subject: [PATCH 28/47] session: add WithPrivacy to Session type --- session/interface.go | 62 +++++++++++++++++++++++--------------------- session/tlv.go | 27 ++++++++++++------- session/tlv_test.go | 2 +- session_rpcserver.go | 2 +- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/session/interface.go b/session/interface.go index 6f9b4bd59..6b355854c 100644 --- a/session/interface.go +++ b/session/interface.go @@ -46,22 +46,23 @@ type FeaturesConfig map[string][]byte // Session is a struct representing a long-term Terminal Connect session. type Session struct { - ID ID - Label string - State State - Type Type - Expiry time.Time - CreatedAt time.Time - RevokedAt time.Time - ServerAddr string - DevServer bool - MacaroonRootKey uint64 - MacaroonRecipe *MacaroonRecipe - PairingSecret [mailbox.NumPassphraseEntropyBytes]byte - LocalPrivateKey *btcec.PrivateKey - LocalPublicKey *btcec.PublicKey - RemotePublicKey *btcec.PublicKey - FeatureConfig *FeaturesConfig + ID ID + Label string + State State + Type Type + Expiry time.Time + CreatedAt time.Time + RevokedAt time.Time + ServerAddr string + DevServer bool + MacaroonRootKey uint64 + MacaroonRecipe *MacaroonRecipe + PairingSecret [mailbox.NumPassphraseEntropyBytes]byte + LocalPrivateKey *btcec.PrivateKey + LocalPublicKey *btcec.PublicKey + RemotePublicKey *btcec.PublicKey + FeatureConfig *FeaturesConfig + WithPrivacyMapper bool } // MacaroonBaker is a function type for baking a super macaroon. @@ -71,7 +72,7 @@ type MacaroonBaker func(ctx context.Context, rootKeyID uint64, // NewSession creates a new session with the given user-defined parameters. func NewSession(label string, typ Type, expiry time.Time, serverAddr string, devServer bool, perms []bakery.Op, caveats []macaroon.Caveat, - featureConfig FeaturesConfig) (*Session, error) { + featureConfig FeaturesConfig, privacy bool) (*Session, error) { _, pairingSecret, err := mailbox.NewPassphraseEntropy() if err != nil { @@ -89,19 +90,20 @@ func NewSession(label string, typ Type, expiry time.Time, serverAddr string, macRootKey := NewSuperMacaroonRootKeyID(macRootKeyBase) sess := &Session{ - ID: macRootKeyBase, - Label: label, - State: StateCreated, - Type: typ, - Expiry: expiry, - CreatedAt: time.Now(), - ServerAddr: serverAddr, - DevServer: devServer, - MacaroonRootKey: macRootKey, - PairingSecret: pairingSecret, - LocalPrivateKey: privateKey, - LocalPublicKey: pubKey, - RemotePublicKey: nil, + ID: macRootKeyBase, + Label: label, + State: StateCreated, + Type: typ, + Expiry: expiry, + CreatedAt: time.Now(), + ServerAddr: serverAddr, + DevServer: devServer, + MacaroonRootKey: macRootKey, + PairingSecret: pairingSecret, + LocalPrivateKey: privateKey, + LocalPublicKey: pubKey, + RemotePublicKey: nil, + WithPrivacyMapper: privacy, } if perms != nil || caveats != nil { diff --git a/session/tlv.go b/session/tlv.go index c237f09d3..f2dbb88c8 100644 --- a/session/tlv.go +++ b/session/tlv.go @@ -26,7 +26,7 @@ const ( typeMacaroonRecipe tlv.Type = 12 typeCreatedAt tlv.Type = 13 typeFeaturesConfig tlv.Type = 14 - typeReservedNum2 tlv.Type = 15 + typeWithPrivacy tlv.Type = 15 typeRevokedAt tlv.Type = 16 // typeMacaroon is no longer used, but we leave it defined for backwards @@ -65,12 +65,17 @@ func SerializeSession(w io.Writer, session *Session) error { privateKey = session.LocalPrivateKey.Serialize() createdAt = uint64(session.CreatedAt.Unix()) revokedAt uint64 + withPrivacy = uint8(0) ) if !session.RevokedAt.IsZero() { revokedAt = uint64(session.RevokedAt.Unix()) } + if session.WithPrivacyMapper { + withPrivacy = 1 + } + if session.DevServer { devServer = 1 } @@ -130,7 +135,9 @@ func SerializeSession(w io.Writer, session *Session) error { } tlvRecords = append( - tlvRecords, tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), + tlvRecords, + tlv.MakePrimitiveRecord(typeWithPrivacy, &withPrivacy), + tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), ) tlvStream, err := tlv.NewStream(tlvRecords...) @@ -145,13 +152,13 @@ func SerializeSession(w io.Writer, session *Session) error { // the data to be encoded in the tlv format. func DeserializeSession(r io.Reader) (*Session, error) { var ( - session = &Session{} - label, serverAddr []byte - pairingSecret, privateKey []byte - state, typ, devServer uint8 - expiry, createdAt, revokedAt uint64 - macRecipe MacaroonRecipe - featureConfig FeaturesConfig + session = &Session{} + label, serverAddr []byte + pairingSecret, privateKey []byte + state, typ, devServer, privacy uint8 + expiry, createdAt, revokedAt uint64 + macRecipe MacaroonRecipe + featureConfig FeaturesConfig ) tlvStream, err := tlv.NewStream( tlv.MakePrimitiveRecord(typeLabel, &label), @@ -177,6 +184,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { typeFeaturesConfig, &featureConfig, nil, featureConfigEncoder, featureConfigDecoder, ), + tlv.MakePrimitiveRecord(typeWithPrivacy, &privacy), tlv.MakePrimitiveRecord(typeRevokedAt, &revokedAt), ) if err != nil { @@ -196,6 +204,7 @@ func DeserializeSession(r io.Reader) (*Session, error) { session.CreatedAt = time.Unix(int64(createdAt), 0) session.ServerAddr = string(serverAddr) session.DevServer = devServer == 1 + session.WithPrivacyMapper = privacy == 1 if revokedAt != 0 { session.RevokedAt = time.Unix(int64(revokedAt), 0) diff --git a/session/tlv_test.go b/session/tlv_test.go index 8c2d55390..f1d211dd8 100644 --- a/session/tlv_test.go +++ b/session/tlv_test.go @@ -87,7 +87,7 @@ func TestSerializeDeserializeSession(t *testing.T) { test.name, test.sessType, time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC), "foo.bar.baz:1234", true, test.perms, - test.caveats, test.featureConfig, + test.caveats, test.featureConfig, true, ) require.NoError(t, err) diff --git a/session_rpcserver.go b/session_rpcserver.go index 8fc8a5b01..e654e9e95 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -249,7 +249,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, sess, err := session.NewSession( req.Label, typ, expiry, req.MailboxServerAddr, req.DevServer, - uniquePermissions, caveats, nil, + uniquePermissions, caveats, nil, false, ) if err != nil { return nil, fmt.Errorf("error creating new session: %v", err) From e8065428e62681b52f1223e194787503b05c990f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 8 Sep 2022 13:25:27 +0200 Subject: [PATCH 29/47] firewall: add WithPrivacy to RequestInfo --- firewall/caveats.go | 21 +++++++++++++++++++++ firewall/request_info.go | 12 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/firewall/caveats.go b/firewall/caveats.go index e223bc6cd..464a7ea26 100644 --- a/firewall/caveats.go +++ b/firewall/caveats.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/lightningnetwork/lnd/macaroons" + "gopkg.in/macaroon.v2" ) const ( @@ -20,6 +21,11 @@ const ( // MetaRulesValuePrefix is the static prefix a macaroon caveat value has // to mark the beginning of the rules list JSON data. MetaRulesValuePrefix = "rules" + + // CondPrivacy is the name of the custom caveat that will + // instruct lnd to send all requests with this caveat to this + // interceptor. + CondPrivacy = "privacy" ) var ( @@ -35,6 +41,15 @@ var ( macaroons.CondLndCustom, RuleEnforcerCaveat, MetaRulesValuePrefix) + // MetaPrivacyCaveatPrefix is the caveat prefix that will be used to + // identify the privacy mapper caveat. + MetaPrivacyCaveatPrefix = fmt.Sprintf("%s %s", macaroons.CondLndCustom, + CondPrivacy) + + // MetaPrivacyCaveat is the caveat required to ensure that the + // privacy mapper is activated as an interceptor for a request. + MetaPrivacyCaveat = macaroon.Caveat{Id: []byte(MetaPrivacyCaveatPrefix)} + // ErrNoMetaInfoCaveat is the error that is returned if a caveat doesn't // have the prefix to be recognized as a meta information caveat. ErrNoMetaInfoCaveat = fmt.Errorf("not a meta info caveat") @@ -150,3 +165,9 @@ func ParseRuleCaveat(caveat string) (*InterceptRules, error) { return &rules, nil } + +// IsPrivacyCaveat returns true if the given caveat string is a privacy mapper +// caveat. +func IsPrivacyCaveat(caveat string) bool { + return strings.Contains(caveat, MetaPrivacyCaveatPrefix) +} diff --git a/firewall/request_info.go b/firewall/request_info.go index 5f2914b21..10a524934 100644 --- a/firewall/request_info.go +++ b/firewall/request_info.go @@ -37,6 +37,7 @@ type RequestInfo struct { Caveats []string MetaInfo *InterceptMetaInfo Rules *InterceptRules + WithPrivacy bool } // NewInfoFromRequest parses the given RPC middleware interception request and @@ -99,7 +100,8 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { if err == nil { ri.MetaInfo = metaInfo - // The same caveat can't be a meta info and rule list. + // The same caveat can't be a meta info and a rule list + // or a privacy caveat. continue } @@ -109,6 +111,14 @@ func NewInfoFromRequest(req *lnrpc.RPCMiddlewareRequest) (*RequestInfo, error) { rules, err := ParseRuleCaveat(ri.Caveats[idx]) if err == nil { ri.Rules = rules + + // The same caveat can't be a rule list and a privacy + // caveat. + continue + } + + if IsPrivacyCaveat(ri.Caveats[idx]) { + ri.WithPrivacy = true } } From a7c5e97401414dc271d8bf03e7d358f4cce768ce Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:27:40 +0200 Subject: [PATCH 30/47] multi: implement rule-enforcer --- firewall/rule_enforcer.go | 339 +++++++++++++++++++++++++++++++++++++- terminal.go | 32 +++- 2 files changed, 358 insertions(+), 13 deletions(-) diff --git a/firewall/rule_enforcer.go b/firewall/rule_enforcer.go index 3289e9162..8b2c03060 100644 --- a/firewall/rule_enforcer.go +++ b/firewall/rule_enforcer.go @@ -4,8 +4,16 @@ import ( "context" "fmt" + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/perms" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lightning-terminal/rules" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) const ( @@ -20,6 +28,47 @@ var _ mid.RequestInterceptor = (*RuleEnforcer)(nil) // RuleEnforcer is a RequestInterceptor that makes sure all firewall related // custom caveats in a macaroon are properly enforced. type RuleEnforcer struct { + ruleDB firewalldb.RulesDB + actionsDB firewalldb.ActionReadDBGetter + markActionErrored func(reqID uint64, reason string) error + newPrivMap firewalldb.NewPrivacyMapDB + + permsMgr *perms.Manager + getFeaturePerms featurePerms + + nodeID [33]byte + + routerClient lndclient.RouterClient + lndClient lndclient.LightningClient + + ruleMgrs rules.ManagerSet +} + +// featurePerms defines the signature of a function that can be used to fetch +// feature permissions. +type featurePerms func(ctx context.Context) (map[string]map[string]bool, error) + +// NewRuleEnforcer constructs a new RuleEnforcer instance. +func NewRuleEnforcer(ruleDB firewalldb.RulesDB, + actionsDB firewalldb.ActionReadDBGetter, getFeaturePerms featurePerms, + permsMgr *perms.Manager, nodeID [33]byte, + routerClient lndclient.RouterClient, + lndClient lndclient.LightningClient, ruleMgrs rules.ManagerSet, + markActionErrored func(reqID uint64, reason string) error, + privMap firewalldb.NewPrivacyMapDB) *RuleEnforcer { + + return &RuleEnforcer{ + ruleDB: ruleDB, + actionsDB: actionsDB, + permsMgr: permsMgr, + getFeaturePerms: getFeaturePerms, + nodeID: nodeID, + routerClient: routerClient, + lndClient: lndClient, + ruleMgrs: ruleMgrs, + markActionErrored: markActionErrored, + newPrivMap: privMap, + } } // Name returns the name of the interceptor. @@ -41,7 +90,7 @@ func (r *RuleEnforcer) CustomCaveatName() string { // Intercept processes an RPC middleware interception request and returns the // interception result which either accepts or rejects the intercepted message. -func (r *RuleEnforcer) Intercept(_ context.Context, +func (r *RuleEnforcer) Intercept(ctx context.Context, req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) { ri, err := NewInfoFromRequest(req) @@ -50,15 +99,287 @@ func (r *RuleEnforcer) Intercept(_ context.Context, "interception request: %v", err) } - log.Infof("Enforcing rule %v", ri) + if ri.Rules == nil { + return mid.RPCOk(req) + } + + log.Tracef("RuleEnforcer: Intercepting %v", ri) + + if ri.MetaInfo == nil { + return mid.RPCErrString(req, "missing MetaInfo") + } + + // Ensure that the specified feature name is one listed in the macaroon. + featureName := ri.MetaInfo.Feature + _, ok := ri.Rules.FeatureRules[featureName] + if len(ri.Rules.FeatureRules) != 0 && !ok { + return mid.RPCErrString(req, "feature %s does not correspond "+ + "to a feature specified in the macaroon caveat", + featureName) + } + + // Ensure that the feature specified in the MetaInfo is one that we + // know about from our last interaction with the Autopilot server. + featurePerms, err := r.getFeaturePerms(ctx) + if err != nil { + return mid.RPCErrString(req, "unable to get feature "+ + "permissions") + } + + perms, ok := featurePerms[featureName] + if !ok { + return mid.RPCErrString(req, "feature %s is not a known "+ + "feature", featureName) + } + + // Then check that this URI is allowed given the list of perms the + // Autopilot told us this feature could use. + if !perms[ri.URI] { + return mid.RPCErrString(req, "Method %s is not allowed for "+ + "feature %s", ri.URI, featureName) + } + + switch ri.MWRequestType { + case MWRequestTypeStreamAuth: + return mid.RPCOk(req) + + // Parse incoming requests and act on them. + case MWRequestTypeRequest: + replacement, err := r.handleRequest(ctx, ri) + if err != nil { + dbErr := r.markActionErrored(ri.RequestID, err.Error()) + if dbErr != nil { + log.Error("could not mark action for "+ + "request ID %d as Errored: %v", + ri.RequestID, dbErr) + } + + return mid.RPCErr(req, err) + } + + // No error occurred but the request should be replaced with + // the given custom request. Wrap it in the correct RPC + // request of the interceptor now. + if replacement != nil { + return mid.RPCReplacement(req, replacement) + } + + // No error and no replacement, just return an empty request of + // the correct type. + return mid.RPCOk(req) + + // Parse and possibly manipulate outgoing responses. + case MWRequestTypeResponse: + if ri.IsError { + replacementErr, err := r.handleErrorResponse(ctx, ri) + if err != nil { + return mid.RPCErr(req, err) + } + + // No error occurred but the response error should be + // replaced with the given custom error. Wrap it in the + // correct RPC response of the interceptor now. + if replacementErr != nil { + return mid.RPCErrReplacement( + req, replacementErr, + ) + } + + // No error and no replacement, just return an empty + // response of the correct type. + return mid.RPCOk(req) + } + + replacement, err := r.handleResponse(ctx, ri) + if err != nil { + return mid.RPCErr(req, err) + } + + // No error occurred but the response should be replaced with + // the given custom response. Wrap it in the correct RPC + // response of the interceptor now. + if replacement != nil { + return mid.RPCReplacement(req, replacement) + } + + // No error and no replacement, just return an empty response of + // the correct type. + return mid.RPCOk(req) + + default: + return mid.RPCErrString(req, "invalid intercept type: %v", r) + } +} + +// handleRequest gathers the rules that will need to enforced for the given +// feature and runs the request against each of those. +func (r *RuleEnforcer) handleRequest(ctx context.Context, + ri *RequestInfo) (proto.Message, error) { + + sessionID, err := session.IDFromMacaroon(ri.Macaroon) + if err != nil { + return nil, fmt.Errorf("could not extract ID from macaroon") + } + + rules, err := r.collectEnforcers(ri, sessionID) + if err != nil { + return nil, fmt.Errorf("error parsing rules: %v", err) + } + + msg, err := mid.ParseProtobuf( + ri.GRPCMessageType, ri.Serialized, + ) + if err != nil { + return nil, fmt.Errorf("error parsing proto: %v", err) + } + + for _, rule := range rules { + newRequest, err := rule.HandleRequest(ctx, ri.URI, msg) + if err != nil { + st := status.Errorf( + codes.ResourceExhausted, "rule violation: %v", + err, + ) + return nil, st + } + + if newRequest != nil { + msg = newRequest + } + } + + return nil, nil +} + +// handleResponse gathers the rules that will need to be enforced for the given +// feature and runs the response against each of those. +func (r *RuleEnforcer) handleResponse(ctx context.Context, + ri *RequestInfo) (proto.Message, error) { + + sessionID, err := session.IDFromMacaroon(ri.Macaroon) + if err != nil { + return nil, fmt.Errorf("could not extract ID from macaroon") + } + + enforcers, err := r.collectEnforcers(ri, sessionID) + if err != nil { + return nil, fmt.Errorf("error parsing rules: %v", err) + } + + msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized) + if err != nil { + return nil, fmt.Errorf("error parsing proto: %v", err) + } + + for _, enforcer := range enforcers { + newResponse, err := enforcer.HandleResponse(ctx, ri.URI, msg) + if err != nil { + return nil, err + } + + if newResponse != nil { + msg = newResponse + } + } + + return msg, nil +} + +// handleErrorResponse gathers the rules that will need to be enforced for the +// given feature and runs the response error against each of those. +func (r *RuleEnforcer) handleErrorResponse(ctx context.Context, + ri *RequestInfo) (error, error) { + + sessionID, err := session.IDFromMacaroon(ri.Macaroon) + if err != nil { + return nil, fmt.Errorf("could not extract ID from macaroon") + } + + enforcers, err := r.collectEnforcers(ri, sessionID) + if err != nil { + return nil, fmt.Errorf("error parsing rules: %v", err) + } + + parsedErr := mid.ParseResponseErr(ri.Serialized) + + for _, enforcer := range enforcers { + newErr, err := enforcer.HandleErrorResponse( + ctx, ri.URI, parsedErr, + ) + if err != nil { + return nil, err + } + + if newErr != nil { + parsedErr = newErr + } + } + + return parsedErr, nil +} + +// collectRule initialises and returns all the Rules that need to be enforced +// for the given request. +func (r *RuleEnforcer) collectEnforcers(ri *RequestInfo, sessionID session.ID) ( + []rules.Enforcer, error) { + + ruleEnforcers := make( + []rules.Enforcer, 0, + len(ri.Rules.FeatureRules)+len(ri.Rules.SessionRules), + ) + + for rule, value := range ri.Rules.FeatureRules[ri.MetaInfo.Feature] { + r, err := r.initRule( + ri.RequestID, rule, []byte(value), ri.MetaInfo.Feature, + sessionID, false, ri.WithPrivacy, + ) + if err != nil { + return nil, err + } + + ruleEnforcers = append(ruleEnforcers, r) + } + + return ruleEnforcers, nil +} + +// initRule initialises a rule.Rule with any required config values. +func (r *RuleEnforcer) initRule(reqID uint64, name string, value []byte, + featureName string, sessionID session.ID, sessionRule, + privacy bool) (rules.Enforcer, error) { + + ruleValues, err := r.ruleMgrs.InitRuleValues(name, value) + if err != nil { + return nil, err + } + + if privacy { + privMap := r.newPrivMap(sessionID) + ruleValues, err = ruleValues.PseudoToReal(privMap) + if err != nil { + return nil, fmt.Errorf("could not prepare rule "+ + "value: %v", err) + } + } + + allActionsDB := r.actionsDB.GetActionsReadDB(sessionID, featureName) + actionsDB := allActionsDB.FeatureActionsDB() + rulesDB := r.ruleDB.GetKVStores(name, sessionID, featureName) + + if sessionRule { + actionsDB = allActionsDB.SessionActionsDB() + rulesDB = r.ruleDB.GetKVStores(name, sessionID, "") + } - // Enforce actual rules. - numRules := len(ri.Rules.SessionRules) + len(ri.Rules.FeatureRules) - if numRules > 0 { - // TODO(guggero): Implement rules and their enforcement. - log.Debugf("There are %d rules to enforce", numRules) + cfg := &rules.ConfigImpl{ + Stores: rulesDB, + ActionsDB: actionsDB, + MethodPerms: r.permsMgr.URIPermissions, + NodeID: r.nodeID, + RouterClient: r.routerClient, + LndClient: r.lndClient, + ReqID: int64(reqID), } - // Send empty response, accepting the request. - return mid.RPCOk(req) + return r.ruleMgrs.InitEnforcer(cfg, name, ruleValues) } diff --git a/terminal.go b/terminal.go index d194cc777..78f30e11d 100644 --- a/terminal.go +++ b/terminal.go @@ -725,15 +725,39 @@ func (g *LightningTerminal) startSubservers() error { g.accountServiceStarted = true requestLogger := firewall.NewRequestLogger(g.firewallDB) + mw := []mid.RequestInterceptor{ + g.accountService, + requestLogger, + } + + if !g.cfg.Autopilot.Disable { + info, err := g.lndClient.Client.GetInfo(ctxc) + if err != nil { + return fmt.Errorf("GetInfo call failed: %v", err) + } + + ruleEnforcer := firewall.NewRuleEnforcer( + g.firewallDB, g.firewallDB, + g.autopilotClient.ListFeaturePerms, + g.permsMgr, info.IdentityPubkey, + g.lndClient.Router, + g.lndClient.Client, g.ruleMgrs, + func(reqID uint64, reason string) error { + return requestLogger.MarkAction( + reqID, firewalldb.ActionStateError, + reason, + ) + }, g.firewallDB.PrivacyDB, + ) + + mw = append(mw, ruleEnforcer) + } // Start the middleware manager. log.Infof("Starting LiT middleware manager") g.middleware = mid.NewManager( g.cfg.RPCMiddleware.InterceptTimeout, - g.lndClient.Client, g.errQueue.ChanIn(), - g.accountService, - requestLogger, - &firewall.RuleEnforcer{}, + g.lndClient.Client, g.errQueue.ChanIn(), mw..., ) if err = g.middleware.Start(); err != nil { From 3d669d6ac3eb5924a21858e08a186f335c93a224 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:35:31 +0200 Subject: [PATCH 31/47] firewall: Add Privacy Mapper interceptor --- firewall/privacy_mapper.go | 494 ++++++++++++++++++++++++++++++++ firewall/privacy_mapper_test.go | 368 ++++++++++++++++++++++++ 2 files changed, 862 insertions(+) create mode 100644 firewall/privacy_mapper.go create mode 100644 firewall/privacy_mapper_test.go diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go new file mode 100644 index 000000000..2f63c37c0 --- /dev/null +++ b/firewall/privacy_mapper.go @@ -0,0 +1,494 @@ +package firewall + +import ( + "context" + "errors" + "fmt" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/protobuf/proto" +) + +// privacyMapperName is the name of the RequestLogger interceptor. +const privacyMapperName = "lit-privacy-mapper" + +var ( + // ErrNotSupportedByPrivacyMapper indicates that the invoked RPC method + // is not supported by the privacy mapper. + ErrNotSupportedByPrivacyMapper = errors.New("this RPC call is not " + + "supported by the privacy mapper interceptor") +) + +// A compile-time assertion that PrivacyMapper is a +// rpcmiddleware.RequestInterceptor. +var _ mid.RequestInterceptor = (*PrivacyMapper)(nil) + +// PrivacyMapper is a RequestInterceptor that maps any pseudo names in certain +// requests to their real values and vice versa for responses. +type PrivacyMapper struct { + newDB firewalldb.NewPrivacyMapDB +} + +// NewPrivacyMapper returns a new instance of PrivacyMapper. +func NewPrivacyMapper(newDB firewalldb.NewPrivacyMapDB) *PrivacyMapper { + return &PrivacyMapper{ + newDB: newDB, + } +} + +// Name returns the name of the interceptor. +func (p *PrivacyMapper) Name() string { + return privacyMapperName +} + +// ReadOnly returns true if this interceptor should be registered in read-only +// mode. In read-only mode no custom caveat name can be specified. +func (p *PrivacyMapper) ReadOnly() bool { + return false +} + +// CustomCaveatName returns the name of the custom caveat that is expected to be +// handled by this interceptor. Cannot be specified in read-only mode. +func (p *PrivacyMapper) CustomCaveatName() string { + return CondPrivacy +} + +// Intercept processes an RPC middleware interception request and returns the +// interception result which either accepts or rejects the intercepted message. +func (p *PrivacyMapper) Intercept(ctx context.Context, + req *lnrpc.RPCMiddlewareRequest) (*lnrpc.RPCMiddlewareResponse, error) { + + ri, err := NewInfoFromRequest(req) + if err != nil { + return nil, fmt.Errorf("error parsing incoming RPC middleware "+ + "interception request: %v", err) + } + + sessionID, err := session.IDFromMacaroon(ri.Macaroon) + if err != nil { + return nil, fmt.Errorf("could not extract ID from macaroon") + } + + log.Tracef("PrivacyMapper: Intercepting %v", ri) + + switch r := req.InterceptType.(type) { + case *lnrpc.RPCMiddlewareRequest_StreamAuth: + return mid.RPCErr(req, fmt.Errorf("streams unsupported")) + + // Parse incoming requests and act on them. + case *lnrpc.RPCMiddlewareRequest_Request: + msg, err := mid.ParseProtobuf( + r.Request.TypeName, r.Request.Serialized, + ) + if err != nil { + return mid.RPCErrString(req, "error parsing proto: %v", + err) + } + + replacement, err := p.checkAndReplaceIncomingRequest( + ctx, r.Request.MethodFullUri, msg, sessionID, + ) + if err != nil { + return mid.RPCErr(req, err) + } + + // No error occurred but the response should be replaced with + // the given custom response. Wrap it in the correct RPC + // response of the interceptor now. + if replacement != nil { + return mid.RPCReplacement(req, replacement) + } + + // No error and no replacement, just return an empty response of + // the correct type. + return mid.RPCOk(req) + + // Parse and possibly manipulate outgoing responses. + case *lnrpc.RPCMiddlewareRequest_Response: + if ri.IsError { + // TODO(elle): should we replace all litd errors with + // a generic error? + return mid.RPCOk(req) + } + + msg, err := mid.ParseProtobuf( + r.Response.TypeName, r.Response.Serialized, + ) + if err != nil { + return mid.RPCErrString(req, "error parsing proto: %v", + err) + } + + replacement, err := p.replaceOutgoingResponse( + ctx, r.Response.MethodFullUri, msg, sessionID, + ) + if err != nil { + return mid.RPCErr(req, err) + } + + // No error occurred but the response should be replaced with + // the given custom response. Wrap it in the correct RPC + // response of the interceptor now. + if replacement != nil { + return mid.RPCReplacement(req, replacement) + } + + // No error and no replacement, just return an empty response of + // the correct type. + return mid.RPCOk(req) + + default: + return mid.RPCErrString(req, "invalid intercept type: %v", r) + } +} + +// checkAndReplaceIncomingRequest inspects an incoming request and optionally +// modifies some of the request parameters. +func (p *PrivacyMapper) checkAndReplaceIncomingRequest(ctx context.Context, + uri string, req proto.Message, sessionID session.ID) (proto.Message, + error) { + + db := p.newDB(sessionID) + + // If we don't have a handler for the URI, we don't allow the request + // to go through. + checker, ok := p.checkers(db)[uri] + if !ok { + return nil, ErrNotSupportedByPrivacyMapper + } + + // This is just a sanity check to make sure the implementation for the + // checker actually matches the correct request type. + if !checker.HandlesRequest(req.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept request of type %v", uri, + req.ProtoReflect().Type()) + } + + return checker.HandleRequest(ctx, req) +} + +// replaceOutgoingResponse inspects the responses before sending them out to the +// client and replaces them if needed. +func (p *PrivacyMapper) replaceOutgoingResponse(ctx context.Context, uri string, + resp proto.Message, sessionID session.ID) (proto.Message, error) { + + db := p.newDB(sessionID) + + // If we don't have a handler for the URI, we don't allow the response + // to go to avoid accidental leaks. + checker, ok := p.checkers(db)[uri] + if !ok { + return nil, ErrNotSupportedByPrivacyMapper + } + + // This is just a sanity check to make sure the implementation for the + // checker actually matches the correct response type. + if !checker.HandlesResponse(resp.ProtoReflect().Type()) { + return nil, fmt.Errorf("invalid implementation, checker for "+ + "URI %s does not accept response of type %v", uri, + resp.ProtoReflect().Type()) + } + + return checker.HandleResponse(ctx, resp) +} + +func (p *PrivacyMapper) checkers( + db firewalldb.PrivacyMapDB) map[string]mid.RoundTripChecker { + + return map[string]mid.RoundTripChecker{ + "/lnrpc.Lightning/GetInfo": mid.NewResponseRewriter( + &lnrpc.GetInfoRequest{}, &lnrpc.GetInfoResponse{}, + handleGetInfoRequest(db), mid.PassThroughErrorHandler, + ), + "/lnrpc.Lightning/ForwardingHistory": mid.NewResponseRewriter( + &lnrpc.ForwardingHistoryRequest{}, + &lnrpc.ForwardingHistoryResponse{}, + handleFwdHistoryResponse(db), + mid.PassThroughErrorHandler, + ), + "/lnrpc.Lightning/FeeReport": mid.NewResponseRewriter( + &lnrpc.FeeReportRequest{}, &lnrpc.FeeReportResponse{}, + handleFeeReportResponse(db), + mid.PassThroughErrorHandler, + ), + "/lnrpc.Lightning/ListChannels": mid.NewFullRewriter( + &lnrpc.ListChannelsRequest{}, + &lnrpc.ListChannelsResponse{}, + handleListChannelsRequest(db), + handleListChannelsResponse(db), + mid.PassThroughErrorHandler, + ), + "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewFullRewriter( + &lnrpc.PolicyUpdateRequest{}, + &lnrpc.PolicyUpdateResponse{}, + handleUpdatePolicyRequest(db), + handleUpdatePolicyResponse(db), + mid.PassThroughErrorHandler, + ), + } +} + +func handleGetInfoRequest(db firewalldb.PrivacyMapDB) func(ctx context.Context, + r *lnrpc.GetInfoResponse) (proto.Message, error) { + + return func(ctx context.Context, r *lnrpc.GetInfoResponse) ( + proto.Message, error) { + + err := db.Update( + func(tx firewalldb.PrivacyMapTx) error { + var err error + pk, err := firewalldb.HideString( + tx, r.IdentityPubkey, + ) + if err != nil { + return err + } + + r.IdentityPubkey = pk + return nil + }, + ) + if err != nil { + return nil, err + } + + // Hide our Alias and URI from the autopilot + // server. + r.Alias = "" + r.Uris = nil + + return r, nil + } +} + +func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.ForwardingHistoryResponse) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.ForwardingHistoryResponse) ( + proto.Message, error) { + + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { + for _, fe := range r.ForwardingEvents { + chanIn, err := firewalldb.HideUint64( + tx, fe.ChanIdIn, + ) + if err != nil { + return err + } + fe.ChanIdIn = chanIn + + chanOut, err := firewalldb.HideUint64( + tx, fe.ChanIdOut, + ) + if err != nil { + return err + } + fe.ChanIdOut = chanOut + } + return nil + }) + if err != nil { + return nil, err + } + + return r, nil + } +} + +func handleFeeReportResponse(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.FeeReportResponse) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.FeeReportResponse) ( + proto.Message, error) { + + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { + for _, c := range r.ChannelFees { + chanID, err := firewalldb.HideUint64( + tx, c.ChanId, + ) + if err != nil { + return err + } + + chanPoint, err := firewalldb.HideChanPointStr( + tx, c.ChannelPoint, + ) + if err != nil { + return err + } + + c.ChannelPoint = chanPoint + c.ChanId = chanID + } + + return nil + }) + if err != nil { + return nil, err + } + + return r, nil + } +} + +func handleListChannelsRequest(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.ListChannelsRequest) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.ListChannelsRequest) ( + proto.Message, error) { + + if len(r.Peer) == 0 { + return nil, nil + } + + err := db.View(func(tx firewalldb.PrivacyMapTx) error { + peer, err := firewalldb.RevealBytes(tx, r.Peer) + if err != nil { + return err + } + + r.Peer = peer + return nil + }) + if err != nil { + return nil, err + } + + return r, nil + } +} + +func handleListChannelsResponse(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.ListChannelsResponse) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.ListChannelsResponse) ( + proto.Message, error) { + + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { + for i, c := range r.Channels { + pk, err := firewalldb.HideString( + tx, c.RemotePubkey, + ) + if err != nil { + return err + } + r.Channels[i].RemotePubkey = pk + + cp, err := firewalldb.HideChanPointStr( + tx, c.ChannelPoint, + ) + if err != nil { + return err + } + r.Channels[i].ChannelPoint = cp + + cid, err := firewalldb.HideUint64(tx, c.ChanId) + if err != nil { + return err + } + r.Channels[i].ChanId = cid + } + + return nil + }) + if err != nil { + return nil, err + } + + return r, nil + } +} + +func handleUpdatePolicyRequest(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.PolicyUpdateRequest) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.PolicyUpdateRequest) ( + proto.Message, error) { + + chanPoint := r.GetChanPoint() + + // If no channel point is specified then the + // update request applies globally. + if chanPoint == nil { + return nil, nil + } + + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + return nil, err + } + + index := chanPoint.GetOutputIndex() + + var ( + newTxid string + newIndex uint32 + ) + err = db.View(func(tx firewalldb.PrivacyMapTx) error { + var err error + newTxid, newIndex, err = firewalldb.RevealChanPoint( + tx, txid.String(), index, + ) + return err + }) + if err != nil { + return nil, err + } + + r.Scope = &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: newTxid, + }, + OutputIndex: newIndex, + }, + } + + return r, nil + } +} + +func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB) func( + ctx context.Context, r *lnrpc.PolicyUpdateResponse) (proto.Message, + error) { + + return func(ctx context.Context, r *lnrpc.PolicyUpdateResponse) ( + proto.Message, error) { + + if len(r.FailedUpdates) == 0 { + return nil, nil + } + + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { + for _, u := range r.FailedUpdates { + txid, index, err := firewalldb.HideChanPoint( + tx, u.Outpoint.TxidStr, + u.Outpoint.OutputIndex, + ) + if err != nil { + return err + } + + u.Outpoint.TxidBytes = nil + u.Outpoint.TxidStr = txid + u.Outpoint.OutputIndex = index + } + + return nil + }) + if err != nil { + return nil, err + } + + return r, nil + } +} diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go new file mode 100644 index 000000000..c6643c772 --- /dev/null +++ b/firewall/privacy_mapper_test.go @@ -0,0 +1,368 @@ +package firewall + +import ( + "context" + "testing" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/rpcperms" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +// TestPrivacyMapper tests that the PrivacyMapper correctly intercepts specific +// RPC calls. +func TestPrivacyMapper(t *testing.T) { + tests := []struct { + name string + uri string + msgType rpcperms.InterceptType + msg proto.Message + expectedReplacement proto.Message + }{ + { + name: "GetInfo Response", + uri: "/lnrpc.Lightning/GetInfo", + msgType: rpcperms.TypeResponse, + msg: &lnrpc.GetInfoResponse{ + Alias: "Tinker Bell", + IdentityPubkey: "Tinker Bell's pub key", + Uris: []string{ + "Neverland 1", + "Neverland 2", + }, + }, + expectedReplacement: &lnrpc.GetInfoResponse{ + IdentityPubkey: "a44ef01c3bff970ef495c", + }, + }, + { + name: "ForwardingHistory Response", + uri: "/lnrpc.Lightning/ForwardingHistory", + msgType: rpcperms.TypeResponse, + msg: &lnrpc.ForwardingHistoryResponse{ + ForwardingEvents: []*lnrpc.ForwardingEvent{ + { + AmtIn: 100, + ChanIdIn: 123, + ChanIdOut: 321, + }, + { + Fee: 200, + ChanIdIn: 678, + ChanIdOut: 876, + }, + }, + }, + expectedReplacement: &lnrpc.ForwardingHistoryResponse{ + ForwardingEvents: []*lnrpc.ForwardingEvent{ + { + AmtIn: 100, + ChanIdIn: 5178778334600911958, + ChanIdOut: 3446430762436373227, + }, + { + Fee: 200, + ChanIdIn: 8672172843977902018, + ChanIdOut: 1378354177616075123, + }, + }, + }, + }, + { + name: "FeeReport Response", + uri: "/lnrpc.Lightning/FeeReport", + msgType: rpcperms.TypeResponse, + msg: &lnrpc.FeeReportResponse{ + ChannelFees: []*lnrpc.ChannelFeeReport{ + { + ChanId: 123, + ChannelPoint: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:0", + }, + { + ChanId: 321, + ChannelPoint: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:1", + }, + }, + }, + expectedReplacement: &lnrpc.FeeReportResponse{ + ChannelFees: []*lnrpc.ChannelFeeReport{ + { + ChanId: 5178778334600911958, + ChannelPoint: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384:2161781494", + }, + { + ChanId: 3446430762436373227, + ChannelPoint: "45ec471bfccb0b7b9a8bc4008248931c59ad994903e07b54f54821ea3ef5cc5c62:1642614131", + }, + }, + }, + }, + { + name: "ListChannels Request", + uri: "/lnrpc.Lightning/ListChannels", + msgType: rpcperms.TypeRequest, + msg: &lnrpc.ListChannelsRequest{ + Peer: []byte{200, 19, 68, 149}, + }, + expectedReplacement: &lnrpc.ListChannelsRequest{ + Peer: []byte{1, 2, 3, 4}, + }, + }, + { + name: "ListChannels Response", + uri: "/lnrpc.Lightning/ListChannels", + msgType: rpcperms.TypeResponse, + msg: &lnrpc.ListChannelsResponse{ + Channels: []*lnrpc.Channel{ + { + RemotePubkey: "01020304", + ChanId: 123, + ChannelPoint: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:0", + }, + }, + }, + expectedReplacement: &lnrpc.ListChannelsResponse{ + Channels: []*lnrpc.Channel{ + { + RemotePubkey: "c8134495", + ChanId: 5178778334600911958, + ChannelPoint: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384:2161781494", + }, + }, + }, + }, + { + name: "UpdateChannelPolicy Request txid string", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msgType: rpcperms.TypeRequest, + msg: &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384", + }, + OutputIndex: 2161781494, + }, + }, + }, + expectedReplacement: &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + }, + OutputIndex: 0, + }, + }, + }, + }, + { + name: "UpdateChannelPolicy Request txid bytes", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msgType: rpcperms.TypeRequest, + msg: &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ + FundingTxidBytes: []byte{132, 227, 191, 172, 166, 31, 110, 128, 103, 165, 12, 12, 247, 8, 172, 203, 165, 227, 234, 1, 183, 179, 19, 52, 255, 25, 25, 166, 102, 246, 126, 9}, + }, + OutputIndex: 2161781494, + }, + }, + }, + expectedReplacement: &lnrpc.PolicyUpdateRequest{ + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + }, + OutputIndex: 0, + }, + }, + }, + }, + { + name: "UpdateChannelPolicy Response", + uri: "/lnrpc.Lightning/UpdateChannelPolicy", + msgType: rpcperms.TypeResponse, + msg: &lnrpc.PolicyUpdateResponse{ + FailedUpdates: []*lnrpc.FailedUpdate{ + { + Outpoint: &lnrpc.OutPoint{ + TxidStr: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd", + OutputIndex: 0, + }, + }, + }, + }, + expectedReplacement: &lnrpc.PolicyUpdateResponse{ + FailedUpdates: []*lnrpc.FailedUpdate{ + { + Outpoint: &lnrpc.OutPoint{ + TxidStr: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384", + OutputIndex: 2161781494, + }, + }, + }, + }, + }, + } + + decodedID := &lnrpc.MacaroonId{ + StorageId: []byte("123"), + } + b, err := proto.Marshal(decodedID) + require.NoError(t, err) + + rawID := make([]byte, len(b)+1) + rawID[0] = byte(bakery.LatestVersion) + copy(rawID[1:], b) + + mac, err := macaroon.New( + []byte("123"), rawID, "", macaroon.V2, + ) + require.NoError(t, err) + + macBytes, err := mac.MarshalBinary() + require.NoError(t, err) + + sessionID, err := session.IDFromMacaroon(mac) + require.NoError(t, err) + + mapPreloadRealToPseudo := map[string]string{ + "Tinker Bell's pub key": "a44ef01c3bff970ef495c", + "000000000000007b": "47deb774fc605c56", + "0000000000000141": "2fd42e84b9ffaaeb", + "00000000000002a6": "7859bf41241787c2", + "000000000000036c": "1320e5d25b7b5973", + "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:0": "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384:2161781494", + "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:1": "45ec471bfccb0b7b9a8bc4008248931c59ad994903e07b54f54821ea3ef5cc5c62:1642614131", + "01020304": "c8134495", + } + + db := newMockDB(t, mapPreloadRealToPseudo, sessionID) + p := NewPrivacyMapper(db.NewSessionDB) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + rawMsg, err := proto.Marshal(test.msg) + require.NoError(t, err) + + interceptReq := &rpcperms.InterceptionRequest{ + Type: test.msgType, + Macaroon: mac, + RawMacaroon: macBytes, + FullURI: test.uri, + ProtoSerialized: rawMsg, + ProtoTypeName: string( + proto.MessageName(test.msg), + ), + } + + mwReq, err := interceptReq.ToRPC(1, 2) + require.NoError(t, err) + + resp, err := p.Intercept(context.Background(), mwReq) + require.NoError(t, err) + + feedback := resp.GetFeedback() + if test.expectedReplacement == nil { + require.False(t, feedback.ReplaceResponse) + return + } + + expectedRaw, err := proto.Marshal( + test.expectedReplacement, + ) + require.NoError(t, err) + require.Equal( + t, expectedRaw, feedback.ReplacementSerialized, + ) + }) + } +} + +type mockDB map[string]*mockPrivacyMapDB + +func newMockDB(t *testing.T, preloadRealToPseudo map[string]string, + sessID session.ID) mockDB { + + db := make(mockDB) + sessDB := db.NewSessionDB(sessID) + + _ = sessDB.Update(func(tx firewalldb.PrivacyMapTx) error { + for r, p := range preloadRealToPseudo { + require.NoError(t, tx.NewPair(r, p)) + } + return nil + }) + + return db +} + +func (m mockDB) NewSessionDB(sessionID session.ID) firewalldb.PrivacyMapDB { + db, ok := m[string(sessionID[:])] + if ok { + return db + } + + newDB := newMockPrivacyMapDB() + m[string(sessionID[:])] = newDB + + return newDB +} + +func newMockPrivacyMapDB() *mockPrivacyMapDB { + return &mockPrivacyMapDB{ + r2p: make(map[string]string), + p2r: make(map[string]string), + } +} + +type mockPrivacyMapDB struct { + r2p map[string]string + p2r map[string]string +} + +func (m *mockPrivacyMapDB) Update( + f func(tx firewalldb.PrivacyMapTx) error) error { + + return f(m) +} + +func (m *mockPrivacyMapDB) View( + f func(tx firewalldb.PrivacyMapTx) error) error { + + return f(m) +} + +func (m *mockPrivacyMapDB) NewPair(real, pseudo string) error { + m.r2p[real] = pseudo + m.p2r[pseudo] = real + return nil +} + +func (m *mockPrivacyMapDB) PseudoToReal(pseudo string) (string, error) { + r, ok := m.p2r[pseudo] + if !ok { + return "", firewalldb.ErrNoSuchKeyFound + } + + return r, nil +} + +func (m *mockPrivacyMapDB) RealToPseudo(real string) (string, error) { + p, ok := m.r2p[real] + if !ok { + return "", firewalldb.ErrNoSuchKeyFound + } + + return p, nil +} + +var _ firewalldb.PrivacyMapDB = (*mockPrivacyMapDB)(nil) From f9ffc0cdef2c5fbe62d9d00720b8b1e94439415a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 4 Aug 2022 15:19:55 +0200 Subject: [PATCH 32/47] session: add GetSession method --- session/store.go | 36 ++++++++++++++++++++++++++++++++++-- session_rpcserver.go | 4 ++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/session/store.go b/session/store.go index 049012c39..82abe2e27 100644 --- a/session/store.go +++ b/session/store.go @@ -45,8 +45,36 @@ func (db *DB) StoreSession(session *Session) error { }) } +// GetSession fetches the session with the given key. +func (db *DB) GetSession(key *btcec.PublicKey) (*Session, error) { + var session *Session + err := db.View(func(tx *bbolt.Tx) error { + sessionBucket, err := getBucket(tx, sessionBucketKey) + if err != nil { + return err + } + + v := sessionBucket.Get(key.SerializeCompressed()) + if len(v) == 0 { + return ErrSessionNotFound + } + + session, err = DeserializeSession(bytes.NewReader(v)) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, err + } + + return session, nil +} + // ListSessions returns all sessions currently known to the store. -func (db *DB) ListSessions() ([]*Session, error) { +func (db *DB) ListSessions(filterFn func(s *Session) bool) ([]*Session, error) { var sessions []*Session err := db.View(func(tx *bbolt.Tx) error { sessionBucket, err := getBucket(tx, sessionBucketKey) @@ -65,12 +93,16 @@ func (db *DB) ListSessions() ([]*Session, error) { if err != nil { return err } + + if filterFn != nil && !filterFn(session) { + return nil + } + sessions = append(sessions, session) return nil }) }) - if err != nil { return nil, err } diff --git a/session_rpcserver.go b/session_rpcserver.go index e654e9e95..15ae65b31 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -92,7 +92,7 @@ func newSessionRPCServer(cfg *sessionRpcServerConfig) (*sessionRpcServer, // requests. This includes resuming all non-revoked sessions. func (s *sessionRpcServer) start() error { // Start up all previously created sessions. - sessions, err := s.db.ListSessions() + sessions, err := s.db.ListSessions(nil) if err != nil { return fmt.Errorf("error listing sessions: %v", err) } @@ -456,7 +456,7 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { func (s *sessionRpcServer) ListSessions(_ context.Context, _ *litrpc.ListSessionsRequest) (*litrpc.ListSessionsResponse, error) { - sessions, err := s.db.ListSessions() + sessions, err := s.db.ListSessions(nil) if err != nil { return nil, fmt.Errorf("error fetching sessions: %v", err) } From 3b67325360fb3f57c8b84868bb3a634d2f72f788 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:33:00 +0200 Subject: [PATCH 33/47] multi: implement lit-autopilot rpc server --- itest/litd_mode_integrated_test.go | 15 + perms/permissions.go | 18 +- session/interface.go | 12 +- session_rpcserver.go | 585 ++++++++++++++++++++++++++++- terminal.go | 6 + 5 files changed, 613 insertions(+), 23 deletions(-) diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 1c0e07f0a..09d6f3469 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -153,6 +153,14 @@ var ( litConn := litrpc.NewAccountsClient(c) return litConn.ListAccounts(ctx, &litrpc.ListAccountsRequest{}) } + litAutopilotRequestFn = func(ctx context.Context, + c grpc.ClientConnInterface) (proto.Message, error) { + + litConn := litrpc.NewAutopilotClient(c) + return litConn.ListAutopilotFeatures( + ctx, &litrpc.ListAutopilotFeaturesRequest{}, + ) + } litMacaroonFn = func(cfg *LitNodeConfig) string { return cfg.LitMacPath } @@ -232,6 +240,13 @@ var ( successPattern: "\"accounts\":[]", allowedThroughLNC: false, grpcWebURI: "/litrpc.Accounts/ListAccounts", + }, { + name: "litrpc-autopilot", + macaroonFn: litMacaroonFn, + requestFn: litAutopilotRequestFn, + successPattern: "\"features\":{", + allowedThroughLNC: true, + grpcWebURI: "/litrpc.Autopilot/ListAutopilotFeatures", }} // customURIs is a map of endpoint URIs that we want to allow via a diff --git a/perms/permissions.go b/perms/permissions.go index e3adfd1be..4b4e80cd7 100644 --- a/perms/permissions.go +++ b/perms/permissions.go @@ -62,10 +62,26 @@ var ( Entity: "account", Action: "write", }}, - "/litrpc.Lit/ListActions": {{ + "/litrpc.Firewall/ListActions": {{ Entity: "actions", Action: "read", }}, + "/litrpc.Autopilot/ListAutopilotFeatures": {{ + Entity: "autopilot", + Action: "read", + }}, + "/litrpc.Autopilot/AddAutopilotSession": {{ + Entity: "autopilot", + Action: "write", + }}, + "/litrpc.Autopilot/ListAutopilotSessions": {{ + Entity: "autopilot", + Action: "read", + }}, + "/litrpc.Autopilot/RevokeAutopilotSession": {{ + Entity: "autopilot", + Action: "write", + }}, } // whiteListedLNDMethods is a map of all lnd RPC methods that don't diff --git a/session/interface.go b/session/interface.go index 6b355854c..a87d64f80 100644 --- a/session/interface.go +++ b/session/interface.go @@ -15,12 +15,12 @@ import ( type Type uint8 const ( - TypeMacaroonReadonly Type = 0 - TypeMacaroonAdmin Type = 1 - TypeMacaroonCustom Type = 2 - TypeUIPassword Type = 3 - typeReservedForFutureUse Type = 4 - TypeMacaroonAccount Type = 5 + TypeMacaroonReadonly Type = 0 + TypeMacaroonAdmin Type = 1 + TypeMacaroonCustom Type = 2 + TypeUIPassword Type = 3 + TypeAutopilot Type = 4 + TypeMacaroonAccount Type = 5 ) // State represents the state of a session. diff --git a/session_rpcserver.go b/session_rpcserver.go index 15ae65b31..a59a51f8b 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -2,6 +2,8 @@ package terminal import ( "context" + "encoding/json" + "errors" "fmt" "strings" "sync" @@ -10,9 +12,12 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/accounts" + "github.com/lightninglabs/lightning-terminal/autopilotserver" + "github.com/lightninglabs/lightning-terminal/firewall" "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/perms" + "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" @@ -33,6 +38,7 @@ const readOnlyAction = "***readonly***" type sessionRpcServer struct { litrpc.UnimplementedSessionsServer litrpc.UnimplementedFirewallServer + litrpc.UnimplementedAutopilotServer cfg *sessionRpcServerConfig db *session.DB @@ -54,6 +60,9 @@ type sessionRpcServerConfig struct { firstConnectionDeadline time.Duration permMgr *perms.Manager actionsDB *firewalldb.DB + autopilot autopilotserver.Autopilot + ruleMgrs rules.ManagerSet + privMap firewalldb.NewPrivacyMapDB } // newSessionRPCServer creates a new sessionRpcServer using the passed config. @@ -96,10 +105,68 @@ func (s *sessionRpcServer) start() error { if err != nil { return fmt.Errorf("error listing sessions: %v", err) } + for _, sess := range sessions { + key := sess.LocalPublicKey.SerializeCompressed() + + if sess.Type == session.TypeAutopilot { + // We only start the autopilot sessions if the autopilot + // client has been enabled. + if s.cfg.autopilot == nil { + continue + } + + // Do a sanity check to ensure that we have the static + // remote pub key stored for this session. This should + // never not be the case. + if sess.RemotePublicKey == nil { + log.Errorf("no static remote key found for "+ + "autopilot session %x", key) + + continue + } + + if sess.State != session.StateInUse && + sess.State != session.StateCreated { + + continue + } + + if sess.Expiry.Before(time.Now()) { + continue + } + + ctx := context.Background() + ctxc, cancel := context.WithTimeout( + ctx, defaultConnectTimeout, + ) + + // Register the session with the autopilot client. + perm, err := s.cfg.autopilot.ActivateSession( + ctxc, sess.LocalPublicKey, + ) + cancel() + if err != nil { + log.Errorf("error activating autopilot "+ + "session (%x) with the client", key, + err) + + if perm { + err := s.db.RevokeSession( + sess.LocalPublicKey, + ) + if err != nil { + log.Errorf("error revoking "+ + "session: %v", err) + } + + continue + } + } + } + if err := s.resumeSession(sess); err != nil { - log.Errorf("error resuming session (%x): %v", - sess.LocalPublicKey.SerializeCompressed(), err) + log.Errorf("error resuming session (%x): %v", key, err) } } @@ -232,8 +299,9 @@ func (s *sessionRpcServer) AddSession(_ context.Context, // No other types are currently supported. default: return nil, fmt.Errorf("invalid session type, only admin, " + - "readonly, account and custom macaroon types " + - "supported in LiT") + "readonly, custom and account macaroon types supported in " + + "LiT. Autopilot sessions must be added using " + + "AddAutoPilotSession method") } // Collect the de-duped permissions. @@ -263,7 +331,7 @@ func (s *sessionRpcServer) AddSession(_ context.Context, return nil, fmt.Errorf("error starting session: %v", err) } - rpcSession, err := marshalRPCSession(sess) + rpcSession, err := s.marshalRPCSession(sess) if err != nil { return nil, fmt.Errorf("error marshaling session: %v", err) } @@ -325,11 +393,14 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { // For custom session types, we use the caveats and permissions that // were persisted on session creation. - case session.TypeMacaroonCustom: - if sess.MacaroonRecipe != nil { - permissions = sess.MacaroonRecipe.Permissions + case session.TypeMacaroonCustom, session.TypeAutopilot: + if sess.MacaroonRecipe == nil { + break } + permissions = sess.MacaroonRecipe.Permissions + caveats = append(caveats, sess.MacaroonRecipe.Caveats...) + // No other types are currently supported. default: log.Debugf("Not resuming session %x with type %d", pubKeyBytes, @@ -436,16 +507,24 @@ func (s *sessionRpcServer) resumeSession(sess *session.Session) error { pubKeyBytes) } + if s.cfg.autopilot != nil { + ctx := context.Background() + ctxc, cancel := context.WithTimeout( + ctx, defaultConnectTimeout, + ) + + s.cfg.autopilot.SessionRevoked(ctxc, pubKey) + cancel() + } + err = s.sessionServer.StopSession(pubKey) if err != nil { - log.Debugf("Error stopping session: "+ - "%v", err) + log.Debugf("Error stopping session: %v", err) } err = s.db.RevokeSession(pubKey) if err != nil { - log.Debugf("error revoking session: "+ - "%v", err) + log.Debugf("error revoking session: %v", err) } }() @@ -465,7 +544,7 @@ func (s *sessionRpcServer) ListSessions(_ context.Context, Sessions: make([]*litrpc.Session, len(sessions)), } for idx, sess := range sessions { - response.Sessions[idx], err = marshalRPCSession(sess) + response.Sessions[idx], err = s.marshalRPCSession(sess) if err != nil { return nil, fmt.Errorf("error marshaling session: %v", err) @@ -477,7 +556,7 @@ func (s *sessionRpcServer) ListSessions(_ context.Context, // RevokeSession revokes a single session and also stops it if it is currently // active. -func (s *sessionRpcServer) RevokeSession(_ context.Context, +func (s *sessionRpcServer) RevokeSession(ctx context.Context, req *litrpc.RevokeSessionRequest) (*litrpc.RevokeSessionResponse, error) { pubKey, err := btcec.ParsePubKey(req.LocalPublicKey) @@ -489,6 +568,10 @@ func (s *sessionRpcServer) RevokeSession(_ context.Context, return nil, fmt.Errorf("error revoking session: %v", err) } + if s.cfg.autopilot != nil { + s.cfg.autopilot.SessionRevoked(ctx, pubKey) + } + // If the session expired already it might not be running anymore. So we // only log possible errors here. if err := s.sessionServer.StopSession(pubKey); err != nil { @@ -604,7 +687,6 @@ func (s *sessionRpcServer) ListActions(_ context.Context, return nil, err } } - resp := make([]*litrpc.Action, len(actions)) for i, a := range actions { state, err := marshalActionState(a.State) @@ -634,8 +716,438 @@ func (s *sessionRpcServer) ListActions(_ context.Context, }, nil } +// ListAutopilotFeatures fetches all the features supported by the autopilot +// server along with the rules that we need to support in order to subscribe +// to those features. +func (s *sessionRpcServer) ListAutopilotFeatures(ctx context.Context, + _ *litrpc.ListAutopilotFeaturesRequest) ( + *litrpc.ListAutopilotFeaturesResponse, error) { + + fs, err := s.cfg.autopilot.ListFeatures(ctx) + if err != nil { + return nil, err + } + + features := make(map[string]*litrpc.Feature, len(fs)) + for i, f := range fs { + rules, upgrade, err := convertRules(s.cfg.ruleMgrs, f.Rules) + if err != nil { + return nil, err + } + + features[i] = &litrpc.Feature{ + Name: f.Name, + Description: f.Description, + Rules: rules, + PermissionsList: marshalPerms(f.Permissions), + RequiresUpgrade: upgrade, + } + } + + return &litrpc.ListAutopilotFeaturesResponse{ + Features: features, + }, nil +} + +// AddAutopilotSession creates a new LNC session and attempts to register it +// with the Autopilot server. +func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, + req *litrpc.AddAutopilotSessionRequest) ( + *litrpc.AddAutopilotSessionResponse, error) { + + if len(req.Features) == 0 { + return nil, fmt.Errorf("must include at least one feature") + } + + expiry := time.Unix(int64(req.ExpiryTimestampSeconds), 0) + if time.Now().After(expiry) { + return nil, fmt.Errorf("expiry must be in the future") + } + + privacy := !req.NoPrivacyMapper + privacyMapPairs := make(map[string]string) + + // First need to fetch all the perms that need to be baked into this + // mac based on the features. + allFeatures, err := s.cfg.autopilot.ListFeatures(ctx) + if err != nil { + return nil, err + } + + // Create lookup map of all the features that autopilot server offers. + autopilotFeatureMap := make(map[string]*autopilotserver.Feature) + for _, f := range allFeatures { + autopilotFeatureMap[f.Name] = f + } + + // allRules represents all the rules that our firewall knows about. + allRules := s.cfg.ruleMgrs.GetAllRules() + + // Check that each requested feature is a valid autopilot feature and + // that the necessary rules for the feature have been specified. + featureRules := make(map[string]map[string]string, len(req.Features)) + for f, rs := range req.Features { + // Check that the features is known by the autopilot server. + autopilotFeature, ok := autopilotFeatureMap[f] + if !ok { + return nil, fmt.Errorf("%s is not a features "+ + "provided by the Autopilot server", f) + } + + // reqRules is the rules specified in the request. + var reqRules []rules.Values + if rs.Rules != nil { + reqRules = make([]rules.Values, 0, len(rs.Rules.Rules)) + for ruleName, rule := range rs.Rules.Rules { + v, err := s.cfg.ruleMgrs.UnmarshalRuleValues( + ruleName, rule, + ) + if err != nil { + return nil, err + } + + if privacy { + var privMapPairs map[string]string + v, privMapPairs, err = v.RealToPseudo() + if err != nil { + return nil, err + } + + for k, v := range privMapPairs { + privacyMapPairs[k] = v + } + } + + reqRules = append(reqRules, v) + } + } + + // Create a lookup map for the rules specified in this feature. + // Also check that each of the rules in the request is one known + // to us. + frs := make(map[string]rules.Values) + for _, r := range reqRules { + frs[r.RuleName()] = r + _, ok := allRules[r.RuleName()] + if !ok { + return nil, fmt.Errorf("%s is not a known rule", + r.RuleName()) + } + } + + // For each of the specified rules, we check that the values + // set for those rules are sane given the bounds provided by + // the autopilot server for the given feature. + for _, r := range reqRules { + ruleName := r.RuleName() + + autopilotSpecs, ok := autopilotFeature.Rules[ruleName] + if !ok { + return nil, fmt.Errorf("autopilot did not "+ + "specify %s as a rule for feature %s", + ruleName, autopilotFeature.Name) + } + + min, err := s.cfg.ruleMgrs.InitRuleValues( + ruleName, autopilotSpecs.MinVal, + ) + if err != nil { + return nil, err + } + + max, err := s.cfg.ruleMgrs.InitRuleValues( + ruleName, autopilotSpecs.MaxVal, + ) + if err != nil { + return nil, err + } + + if err = r.VerifySane(min, max); err != nil { + return nil, fmt.Errorf("rule value for %s "+ + "not valid for feature %s. Expected "+ + "rule value between %s and %s. Got "+ + "%s. %v", ruleName, + autopilotFeature.Name, min, max, r, err) + } + } + + // If the request did not contain specific values for a rule, + // the default Autopilot rule value is used. + finalRules := make( + []rules.Values, 0, len(autopilotFeature.Rules), + ) + for name, values := range autopilotFeature.Rules { + if r, ok := frs[name]; ok { + finalRules = append(finalRules, r) + continue + } + + defaults, err := s.cfg.ruleMgrs.InitRuleValues( + name, values.Default, + ) + if err != nil { + return nil, err + } + + finalRules = append(finalRules, defaults) + } + + featureRules[f], err = marshalRulesToStringMap(finalRules) + if err != nil { + return nil, err + } + } + + interceptRules := &firewall.InterceptRules{ + FeatureRules: featureRules, + } + + // Gather all the permissions we need to add to the macaroon given the + // feature list. + var dedupedPerms = make(map[string]map[string]bool) + for name, feature := range autopilotFeatureMap { + if _, ok := req.Features[name]; !ok { + continue + } + + for _, ops := range feature.Permissions { + for _, op := range ops { + if dedupedPerms[op.Entity] == nil { + dedupedPerms[op.Entity] = make( + map[string]bool, + ) + } + + dedupedPerms[op.Entity][op.Action] = true + } + } + } + + var perms []bakery.Op + for entity, actions := range dedupedPerms { + for action := range actions { + perms = append(perms, bakery.Op{ + Entity: entity, + Action: action, + }) + } + } + + rulesCaveatStr, err := firewall.RulesToCaveat(interceptRules) + if err != nil { + return nil, err + } + + featureConfig := make(map[string][]byte, len(req.Features)) + for name, f := range req.Features { + featureConfig[name] = f.Config + } + + caveats := []macaroon.Caveat{{Id: []byte(rulesCaveatStr)}} + if privacy { + caveats = append(caveats, firewall.MetaPrivacyCaveat) + } + + sess, err := session.NewSession( + req.Label, session.TypeAutopilot, expiry, req.MailboxServerAddr, + req.DevServer, perms, caveats, featureConfig, privacy, + ) + if err != nil { + return nil, fmt.Errorf("error creating new session: %v", err) + } + + // Register all the privacy map pairs for this session ID. + privDB := s.cfg.privMap(sess.ID) + err = privDB.Update(func(tx firewalldb.PrivacyMapTx) error { + for r, p := range privacyMapPairs { + err := tx.NewPair(r, p) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return nil, err + } + + // Attempt to register the session with the Autopilot server. + remoteKey, err := s.cfg.autopilot.RegisterSession( + ctx, sess.LocalPublicKey, sess.ServerAddr, sess.DevServer, + featureConfig, + ) + if err != nil { + return nil, fmt.Errorf("error registering session with "+ + "autopilot server: %v", err) + } + + // We only persist this session if we successfully retrieved the + // autopilot's static key. + sess.RemotePublicKey = remoteKey + if err := s.db.StoreSession(sess); err != nil { + return nil, fmt.Errorf("error storing session: %v", err) + } + + if err := s.resumeSession(sess); err != nil { + return nil, fmt.Errorf("error starting session: %v", err) + } + + rpcSession, err := s.marshalRPCSession(sess) + if err != nil { + return nil, fmt.Errorf("error marshaling session: %v", err) + } + + return &litrpc.AddAutopilotSessionResponse{ + Session: rpcSession, + }, nil +} + +// ListAutopilotSessions fetches and returns all the sessions from the DB that +// are of type TypeAutopilot. +func (s *sessionRpcServer) ListAutopilotSessions(_ context.Context, + _ *litrpc.ListAutopilotSessionsRequest) ( + *litrpc.ListAutopilotSessionsResponse, error) { + + sessions, err := s.db.ListSessions(func(s *session.Session) bool { + return s.Type == session.TypeAutopilot + }) + if err != nil { + return nil, fmt.Errorf("error fetching sessions: %v", err) + } + + response := &litrpc.ListAutopilotSessionsResponse{ + Sessions: make([]*litrpc.Session, len(sessions)), + } + for idx, sess := range sessions { + response.Sessions[idx], err = s.marshalRPCSession(sess) + if err != nil { + return nil, fmt.Errorf("error marshaling session: %v", + err) + } + } + + return response, nil +} + +// RevokeAutopilotSession revokes an autopilot session. +func (s *sessionRpcServer) RevokeAutopilotSession(ctx context.Context, + req *litrpc.RevokeAutopilotSessionRequest) ( + *litrpc.RevokeAutopilotSessionResponse, error) { + + pubKey, err := btcec.ParsePubKey(req.LocalPublicKey) + if err != nil { + return nil, fmt.Errorf("error parsing public key: %v", err) + } + + sess, err := s.db.GetSession(pubKey) + if err != nil { + return nil, err + } + + if sess.Type != session.TypeAutopilot { + return nil, session.ErrSessionNotFound + } + + _, err = s.RevokeSession( + ctx, &litrpc.RevokeSessionRequest{ + LocalPublicKey: req.LocalPublicKey, + }, + ) + if err != nil { + return nil, err + } + + return &litrpc.RevokeAutopilotSessionResponse{}, nil +} + +func marshalRulesToStringMap(rs []rules.Values) (map[string]string, error) { + res := make(map[string]string, len(rs)) + for _, r := range rs { + b, err := json.Marshal(r) + if err != nil { + return nil, err + } + + res[r.RuleName()] = string(b) + } + + return res, nil +} + +// marshalPerms attempts to convert a set of permissions into their RPC +// counterpart. If the list includes a rule that LiT does not know about, a nil +// entry is included in the returned map. A bool is returned that indicates if +// an upgrade is needed in order to enforce an unknown rule. +func convertRules(ruleMgr rules.ManagerSet, + ruleList map[string]*autopilotserver.RuleValues) ( + map[string]*litrpc.RuleValues, bool, error) { + + var ( + upgrade bool + res = make( + map[string]*litrpc.RuleValues, len(ruleList), + ) + knownRules = ruleMgr.GetAllRules() + ) + for name, rule := range ruleList { + known := true + if !knownRules[name] { + upgrade = true + known = false + } + + defaultVals, err := ruleMgr.InitRuleValues(name, rule.Default) + if err != nil { + return nil, false, err + } + + minVals, err := ruleMgr.InitRuleValues(name, rule.MinVal) + if err != nil { + return nil, false, err + } + + maxVals, err := ruleMgr.InitRuleValues(name, rule.MaxVal) + if err != nil { + return nil, false, err + } + + res[name] = &litrpc.RuleValues{ + Known: known, + Defaults: defaultVals.ToProto(), + MinValue: minVals.ToProto(), + MaxValue: maxVals.ToProto(), + } + } + + return res, upgrade, nil +} + +// marshalPerms converts a set of permissions into their RPC counterpart. +func marshalPerms(perms map[string][]bakery.Op) []*litrpc.Permissions { + var res []*litrpc.Permissions + + for method, ops := range perms { + rpcOps := make([]*litrpc.MacaroonPermission, len(ops)) + for i, op := range ops { + rpcOps[i] = &litrpc.MacaroonPermission{ + Entity: op.Entity, + Action: op.Action, + } + } + + res = append(res, &litrpc.Permissions{ + Method: method, + Operations: rpcOps, + }) + } + + return res +} + // marshalRPCSession converts a session into its RPC counterpart. -func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) { +func (s *sessionRpcServer) marshalRPCSession(sess *session.Session) ( + *litrpc.Session, error) { + rpcState, err := marshalRPCState(sess.State) if err != nil { return nil, err @@ -663,6 +1175,40 @@ func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) { revokedAt = uint64(sess.RevokedAt.Unix()) } + featureInfo := make(map[string]*litrpc.RulesMap) + if sess.MacaroonRecipe != nil { + for _, cav := range sess.MacaroonRecipe.Caveats { + info, err := firewall.ParseRuleCaveat(string(cav.Id)) + if errors.Is(err, firewall.ErrNoRulesCaveat) { + continue + } else if err != nil { + return nil, err + } + + for feature, rules := range info.FeatureRules { + ruleMap := make(map[string]*litrpc.RuleValue) + for name, rule := range rules { + val, err := s.cfg.ruleMgrs.InitRuleValues(name, []byte(rule)) + if err != nil { + return nil, err + } + + if sess.WithPrivacyMapper { + db := s.cfg.privMap(sess.ID) + val, err = val.PseudoToReal(db) + if err != nil { + return nil, err + } + } + + ruleMap[name] = val.ToProto() + } + + featureInfo[feature] = &litrpc.RulesMap{Rules: ruleMap} + } + } + } + return &litrpc.Session{ Id: sess.ID[:], Label: sess.Label, @@ -678,6 +1224,7 @@ func marshalRPCSession(sess *session.Session) (*litrpc.Session, error) { CreatedAt: uint64(sess.CreatedAt.Unix()), RevokedAt: revokedAt, MacaroonRecipe: macRecipe, + AutopilotFeatureInfo: featureInfo, }, nil } @@ -747,6 +1294,9 @@ func marshalRPCType(typ session.Type) (litrpc.SessionType, error) { case session.TypeMacaroonAccount: return litrpc.SessionType_TYPE_MACAROON_ACCOUNT, nil + case session.TypeAutopilot: + return litrpc.SessionType_TYPE_AUTOPILOT, nil + default: return 0, fmt.Errorf("unknown type <%d>", typ) } @@ -770,6 +1320,9 @@ func unmarshalRPCType(typ litrpc.SessionType) (session.Type, error) { case litrpc.SessionType_TYPE_MACAROON_ACCOUNT: return session.TypeMacaroonAccount, nil + case litrpc.SessionType_TYPE_AUTOPILOT: + return session.TypeAutopilot, nil + default: return 0, fmt.Errorf("unknown type <%d>", typ) } diff --git a/terminal.go b/terminal.go index 78f30e11d..58ca075db 100644 --- a/terminal.go +++ b/terminal.go @@ -321,6 +321,8 @@ func (g *LightningTerminal) Run() error { firstConnectionDeadline: g.cfg.FirstLNCConnDeadline, permMgr: g.permsMgr, actionsDB: g.firewallDB, + autopilot: g.autopilotClient, + ruleMgrs: g.ruleMgrs, }) if err != nil { return fmt.Errorf("could not create new session rpc "+ @@ -818,6 +820,10 @@ func (g *LightningTerminal) registerSubDaemonGrpcServers(server *grpc.Server, } litrpc.RegisterFirewallServer(server, g.sessionRpcServer) + + if !g.cfg.Autopilot.Disable { + litrpc.RegisterAutopilotServer(server, g.sessionRpcServer) + } } // RegisterRestSubserver is a callback on the lnd.SubserverConfig struct that is From 6de34a89e2ac0e97cdc257544e04c0f3e190cefc Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 8 Jul 2022 14:34:53 +0200 Subject: [PATCH 34/47] litcli: add lit-autopilot commands --- cmd/litcli/autopilot.go | 197 ++++++++++++++++++++++++++++++++++++++++ cmd/litcli/main.go | 1 + cmd/litcli/sessions.go | 58 ++++++------ 3 files changed, 228 insertions(+), 28 deletions(-) create mode 100644 cmd/litcli/autopilot.go diff --git a/cmd/litcli/autopilot.go b/cmd/litcli/autopilot.go new file mode 100644 index 000000000..333118008 --- /dev/null +++ b/cmd/litcli/autopilot.go @@ -0,0 +1,197 @@ +package main + +import ( + "context" + "encoding/hex" + "strconv" + "strings" + "time" + + "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/lightninglabs/lightning-terminal/rules" + "github.com/urfave/cli" +) + +var autopilotCommands = cli.Command{ + Name: "autopilot", + Usage: "manage autopilot sessions", + Category: "Autopilot", + Subcommands: []cli.Command{ + { + Name: "features", + ShortName: "f", + Usage: "List available features", + Action: listFeatures, + }, + { + Name: "add", + ShortName: "a", + Usage: "Initialize an autopilot session", + Action: initAutopilotSession, + Flags: []cli.Flag{ + labelFlag, + expiryFlag, + mailboxServerAddrFlag, + devserver, + cli.StringSliceFlag{ + Name: "feature", + Required: true, + }, + cli.StringFlag{ + Name: "channel-restrict-list", + Usage: "list of channel IDs that the " + + "autopilot server should not " + + "perform actions on. In the " + + "form of: chanID1,chanID2,...", + }, + cli.StringFlag{ + Name: "peer-restrict-list", + Usage: "list of peer IDs that the " + + "autopilot server should not " + + "perform actions on. In the " + + "form of: peerID1,peerID2,...", + }, + }, + }, + { + Name: "revoke", + ShortName: "r", + Usage: "revoke an autopilot session", + Description: "Revoke an active autopilot session", + Action: revokeAutopilotSession, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "localpubkey", + Usage: "local pubkey of the " + + "session to revoke", + Required: true, + }, + }, + }, + }, +} + +func revokeAutopilotSession(ctx *cli.Context) error { + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewAutopilotClient(clientConn) + + pubkey, err := hex.DecodeString(ctx.String("localpubkey")) + if err != nil { + return err + } + + resp, err := client.RevokeAutopilotSession( + ctxb, &litrpc.RevokeAutopilotSessionRequest{ + LocalPublicKey: pubkey, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func listFeatures(ctx *cli.Context) error { + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewAutopilotClient(clientConn) + + resp, err := client.ListAutopilotFeatures( + ctxb, &litrpc.ListAutopilotFeaturesRequest{}, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +func initAutopilotSession(ctx *cli.Context) error { + sessionLength := time.Second * time.Duration(ctx.Uint64("expiry")) + sessionExpiry := time.Now().Add(sessionLength).Unix() + + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewAutopilotClient(clientConn) + + ruleMap := &litrpc.RulesMap{ + Rules: make(map[string]*litrpc.RuleValue), + } + + chanRestrictList := ctx.String("channel-restrict-list") + if chanRestrictList != "" { + var chanIDs []uint64 + chans := strings.Split(chanRestrictList, ",") + for _, c := range chans { + i, err := strconv.ParseUint(c, 10, 64) + if err != nil { + return err + } + chanIDs = append(chanIDs, i) + } + + ruleMap.Rules[rules.ChannelRestrictName] = &litrpc.RuleValue{ + Value: &litrpc.RuleValue_ChannelRestrict{ + ChannelRestrict: &litrpc.ChannelRestrict{ + ChannelIds: chanIDs, + }, + }, + } + } + + peerRestrictList := ctx.String("peer-restrict-list") + if peerRestrictList != "" { + peerIDs := strings.Split(peerRestrictList, ",") + + ruleMap.Rules[rules.PeersRestrictName] = &litrpc.RuleValue{ + Value: &litrpc.RuleValue_PeerRestrict{ + PeerRestrict: &litrpc.PeerRestrict{ + PeerIds: peerIDs, + }, + }, + } + } + + featureMap := make(map[string]*litrpc.FeatureConfig) + for _, feature := range ctx.StringSlice("feature") { + featureMap[feature] = &litrpc.FeatureConfig{ + Rules: ruleMap, + Config: nil, + } + } + + resp, err := client.AddAutopilotSession( + ctxb, &litrpc.AddAutopilotSessionRequest{ + Label: ctx.String("label"), + ExpiryTimestampSeconds: uint64(sessionExpiry), + MailboxServerAddr: ctx.String("mailboxserveraddr"), + DevServer: ctx.Bool("devserver"), + Features: featureMap, + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} diff --git a/cmd/litcli/main.go b/cmd/litcli/main.go index 0163e3640..70d03753b 100644 --- a/cmd/litcli/main.go +++ b/cmd/litcli/main.go @@ -86,6 +86,7 @@ func main() { app.Commands = append(app.Commands, sessionCommands...) app.Commands = append(app.Commands, accountsCommands...) app.Commands = append(app.Commands, listActionsCommand) + app.Commands = append(app.Commands, autopilotCommands) err := app.Run(os.Args) if err != nil { diff --git a/cmd/litcli/sessions.go b/cmd/litcli/sessions.go index 595919327..dff4e484c 100644 --- a/cmd/litcli/sessions.go +++ b/cmd/litcli/sessions.go @@ -15,6 +15,28 @@ var ( // defaultSessionExpiry is the default time a session can be used for. // The current value evaluates to 90 days. defaultSessionExpiry = time.Hour * 24 * 90 + + labelFlag = cli.StringFlag{ + Name: "label", + Usage: "session label", + Required: true, + } + expiryFlag = cli.Uint64Flag{ + Name: "expiry", + Usage: "number of seconds that the session should " + + "remain active", + Value: uint64(defaultSessionExpiry.Seconds()), + } + mailboxServerAddrFlag = cli.StringFlag{ + Name: "mailboxserveraddr", + Usage: "the host:port of the mailbox server to be used", + Value: "mailbox.terminal.lightning.today:443", + } + devserver = cli.BoolFlag{ + Name: "devserver", + Usage: "set to true to skip verification of the " + + "server's tls cert.", + } ) var sessionCommands = []cli.Command{ @@ -38,26 +60,10 @@ var addSessionCommand = cli.Command{ Description: "Add a new active session.", Action: addSession, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "label", - Usage: "session label", - }, - cli.Uint64Flag{ - Name: "expiry", - Usage: "number of seconds that the session should " + - "remain active", - Value: uint64(defaultSessionExpiry.Seconds()), - }, - cli.StringFlag{ - Name: "mailboxserveraddr", - Usage: "the host:port of the mailbox server to be used", - Value: "mailbox.terminal.lightning.today:443", - }, - cli.BoolFlag{ - Name: "devserver", - Usage: "set to true to skip verification of the " + - "server's tls cert.", - }, + labelFlag, + expiryFlag, + mailboxServerAddrFlag, + devserver, cli.StringFlag{ Name: "type", Usage: "session type to be created which will " + @@ -90,11 +96,6 @@ func addSession(ctx *cli.Context) error { defer cleanup() client := litrpc.NewSessionsClient(clientConn) - label := ctx.String("label") - if label == "" { - return fmt.Errorf("must set a label for the session") - } - sessTypeStr := ctx.String("type") sessType, err := parseSessionType(sessTypeStr) if err != nil { @@ -115,7 +116,7 @@ func addSession(ctx *cli.Context) error { ctxb := context.Background() resp, err := client.AddSession( ctxb, &litrpc.AddSessionRequest{ - Label: label, + Label: ctx.String("label"), SessionType: sessType, ExpiryTimestampSeconds: uint64(sessionExpiry), MailboxServerAddr: ctx.String("mailboxserveraddr"), @@ -260,8 +261,9 @@ var revokeSessionCommand = cli.Command{ Action: revokeSession, Flags: []cli.Flag{ cli.StringFlag{ - Name: "localpubkey", - Usage: "local pubkey of the session to revoke", + Name: "localpubkey", + Usage: "local pubkey of the session to revoke", + Required: true, }, }, } From f3d2aeb00a974ea44a3e83ebfddfd2f82090ce00 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 10 Aug 2022 11:18:38 +0200 Subject: [PATCH 35/47] interface: create a new Litd Client interface --- litrpc/interface.go | 63 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 litrpc/interface.go diff --git a/litrpc/interface.go b/litrpc/interface.go new file mode 100644 index 000000000..e4391ddf6 --- /dev/null +++ b/litrpc/interface.go @@ -0,0 +1,63 @@ +package litrpc + +import ( + "github.com/lightninglabs/faraday/frdrpc" + "github.com/lightninglabs/loop/looprpc" + "github.com/lightninglabs/pool/poolrpc" + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc" +) + +// LitdClient is an interface that can be used to access all the subservers +// of Litd. +type LitdClient interface { + // Lnd returns an lnrpc.LightingClient implementation. + Lnd() lnrpc.LightningClient + + // Loop returns a looprpc.SwapClientClient implementation. + Loop() looprpc.SwapClientClient + + // Pool returns a poolrpc.TraderClient implementation. + Pool() poolrpc.TraderClient + + // Faraday returns a frdrpc.FaradayServerClient implementation. + Faraday() frdrpc.FaradayServerClient +} + +// client is an implementation of the LitdClient. +type client struct { + lnd lnrpc.LightningClient + loop looprpc.SwapClientClient + pool poolrpc.TraderClient + faraday frdrpc.FaradayServerClient +} + +// Lnd returns an lnrpc.LightingClient implementation. +func (c *client) Lnd() lnrpc.LightningClient { + return c.lnd +} + +// Loop returns a looprpc.SwapClientClient implementation. +func (c *client) Loop() looprpc.SwapClientClient { + return c.loop +} + +// Pool returns a poolrpc.TraderClient implementation. +func (c *client) Pool() poolrpc.TraderClient { + return c.pool +} + +// Faraday returns a frdrpc.FaradayServerClient implementation. +func (c *client) Faraday() frdrpc.FaradayServerClient { + return c.faraday +} + +// NewLitdClient constructs a new LitdClient from the passed grpc ClientConn. +func NewLitdClient(cc grpc.ClientConnInterface) LitdClient { + return &client{ + lnd: lnrpc.NewLightningClient(cc), + loop: looprpc.NewSwapClientClient(cc), + pool: poolrpc.NewTraderClient(cc), + faraday: frdrpc.NewFaradayServerClient(cc), + } +} From ab347dd6f16a1cc8a7fd48a3efea8fdaf218e3ab Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 8 Sep 2022 13:00:31 +0200 Subject: [PATCH 36/47] multi: add privacy mapper conversion helper --- cmd/litcli/main.go | 1 + cmd/litcli/privacy_map.go | 134 ++++++++++++++ litrpc/firewall.pb.go | 359 +++++++++++++++++++++++++++---------- litrpc/firewall.pb.json.go | 25 +++ litrpc/firewall.proto | 28 +++ litrpc/firewall_grpc.pb.go | 36 ++++ litrpc/lit-accounts.pb.go | 2 +- perms/permissions.go | 4 + session_rpcserver.go | 30 ++++ terminal.go | 1 + 10 files changed, 522 insertions(+), 98 deletions(-) create mode 100644 cmd/litcli/privacy_map.go diff --git a/cmd/litcli/main.go b/cmd/litcli/main.go index 70d03753b..4ccf03ed4 100644 --- a/cmd/litcli/main.go +++ b/cmd/litcli/main.go @@ -86,6 +86,7 @@ func main() { app.Commands = append(app.Commands, sessionCommands...) app.Commands = append(app.Commands, accountsCommands...) app.Commands = append(app.Commands, listActionsCommand) + app.Commands = append(app.Commands, privacyMapCommands) app.Commands = append(app.Commands, autopilotCommands) err := app.Run(os.Args) diff --git a/cmd/litcli/privacy_map.go b/cmd/litcli/privacy_map.go new file mode 100644 index 000000000..e080bd371 --- /dev/null +++ b/cmd/litcli/privacy_map.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "encoding/hex" + "fmt" + + "github.com/lightninglabs/lightning-terminal/firewalldb" + "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/urfave/cli" +) + +var privacyMapCommands = cli.Command{ + Name: "privacy", + ShortName: "p", + Usage: "Access the real-pseudo string pairs of the " + + "privacy mapper", + Category: "Privacy", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "session_id", + Usage: "The id of the session in question", + Required: true, + }, + cli.BoolFlag{ + Name: "realtopseudo", + Usage: "set to true if the input should be " + + "mapped to its pseudo counterpart. " + + "Otherwise the input will be taken " + + "as the pseudo value that should be " + + "mapped to its real counterpart.", + }, + }, + Subcommands: []cli.Command{ + privacyMapConvertStrCommand, + privacyMapConvertUint64Command, + }, +} + +var privacyMapConvertStrCommand = cli.Command{ + Name: "str", + ShortName: "s", + Usage: "convert a string to its real or pseudo counter part", + Action: privacyMapConvertStr, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "input", + Usage: "the string to convert", + Required: true, + }, + }, +} + +func privacyMapConvertStr(ctx *cli.Context) error { + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewFirewallClient(clientConn) + + id, err := hex.DecodeString(ctx.GlobalString("session_id")) + if err != nil { + return err + } + + resp, err := client.PrivacyMapConversion( + ctxb, &litrpc.PrivacyMapConversionRequest{ + SessionId: id, + RealToPseudo: ctx.GlobalBool("realtopseudo"), + Input: ctx.String("input"), + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + + return nil +} + +var privacyMapConvertUint64Command = cli.Command{ + Name: "uint64", + ShortName: "u", + Usage: "convert a uint64 to its real or pseudo counter part", + Action: privacyMapConvertUint64, + Flags: []cli.Flag{ + cli.Uint64Flag{ + Name: "input", + Usage: "the uint64 to convert", + Required: true, + }, + }, +} + +func privacyMapConvertUint64(ctx *cli.Context) error { + ctxb := context.Background() + clientConn, cleanup, err := connectClient(ctx) + if err != nil { + return err + } + defer cleanup() + client := litrpc.NewFirewallClient(clientConn) + + id, err := hex.DecodeString(ctx.GlobalString("session_id")) + if err != nil { + return err + } + + input := firewalldb.Uint64ToStr(ctx.Uint64("input")) + + resp, err := client.PrivacyMapConversion( + ctxb, &litrpc.PrivacyMapConversionRequest{ + SessionId: id, + RealToPseudo: ctx.GlobalBool("realtopseudo"), + Input: input, + }, + ) + if err != nil { + return err + } + + output, err := firewalldb.StrToUint64(resp.Output) + if err != nil { + return err + } + + printRespJSON(&litrpc.PrivacyMapConversionResponse{ + Output: fmt.Sprintf("%d", output), + }) + return nil +} diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index e4e9eb4f4..1aa42ac90 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -83,6 +83,126 @@ func (ActionState) EnumDescriptor() ([]byte, []int) { return file_firewall_proto_rawDescGZIP(), []int{0} } +type PrivacyMapConversionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //If set to true, then the input string will be taken as the real value and + //the response will the the pseudo value it if exists. Otherwise, the input + //string will be assumed to be the pseudo value. + RealToPseudo bool `protobuf:"varint,1,opt,name=real_to_pseudo,json=realToPseudo,proto3" json:"real_to_pseudo,omitempty"` + // + //The session ID under which to search for the real-pseudo pair. + SessionId []byte `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // + //The input to be converted into the real or pseudo value. + Input string `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` +} + +func (x *PrivacyMapConversionRequest) Reset() { + *x = PrivacyMapConversionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_firewall_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrivacyMapConversionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrivacyMapConversionRequest) ProtoMessage() {} + +func (x *PrivacyMapConversionRequest) ProtoReflect() protoreflect.Message { + mi := &file_firewall_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrivacyMapConversionRequest.ProtoReflect.Descriptor instead. +func (*PrivacyMapConversionRequest) Descriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{0} +} + +func (x *PrivacyMapConversionRequest) GetRealToPseudo() bool { + if x != nil { + return x.RealToPseudo + } + return false +} + +func (x *PrivacyMapConversionRequest) GetSessionId() []byte { + if x != nil { + return x.SessionId + } + return nil +} + +func (x *PrivacyMapConversionRequest) GetInput() string { + if x != nil { + return x.Input + } + return "" +} + +type PrivacyMapConversionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // + //The resulting real or pseudo output. + Output string `protobuf:"bytes,1,opt,name=output,proto3" json:"output,omitempty"` +} + +func (x *PrivacyMapConversionResponse) Reset() { + *x = PrivacyMapConversionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_firewall_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrivacyMapConversionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrivacyMapConversionResponse) ProtoMessage() {} + +func (x *PrivacyMapConversionResponse) ProtoReflect() protoreflect.Message { + mi := &file_firewall_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrivacyMapConversionResponse.ProtoReflect.Descriptor instead. +func (*PrivacyMapConversionResponse) Descriptor() ([]byte, []int) { + return file_firewall_proto_rawDescGZIP(), []int{1} +} + +func (x *PrivacyMapConversionResponse) GetOutput() string { + if x != nil { + return x.Output + } + return "" +} + type ListActionsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -138,7 +258,7 @@ type ListActionsRequest struct { func (x *ListActionsRequest) Reset() { *x = ListActionsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_firewall_proto_msgTypes[0] + mi := &file_firewall_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -151,7 +271,7 @@ func (x *ListActionsRequest) String() string { func (*ListActionsRequest) ProtoMessage() {} func (x *ListActionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_firewall_proto_msgTypes[0] + mi := &file_firewall_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -164,7 +284,7 @@ func (x *ListActionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActionsRequest.ProtoReflect.Descriptor instead. func (*ListActionsRequest) Descriptor() ([]byte, []int) { - return file_firewall_proto_rawDescGZIP(), []int{0} + return file_firewall_proto_rawDescGZIP(), []int{2} } func (x *ListActionsRequest) GetFeatureName() string { @@ -265,7 +385,7 @@ type ListActionsResponse struct { func (x *ListActionsResponse) Reset() { *x = ListActionsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_firewall_proto_msgTypes[1] + mi := &file_firewall_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -278,7 +398,7 @@ func (x *ListActionsResponse) String() string { func (*ListActionsResponse) ProtoMessage() {} func (x *ListActionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_firewall_proto_msgTypes[1] + mi := &file_firewall_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -291,7 +411,7 @@ func (x *ListActionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActionsResponse.ProtoReflect.Descriptor instead. func (*ListActionsResponse) Descriptor() ([]byte, []int) { - return file_firewall_proto_rawDescGZIP(), []int{1} + return file_firewall_proto_rawDescGZIP(), []int{3} } func (x *ListActionsResponse) GetActions() []*Action { @@ -360,7 +480,7 @@ type Action struct { func (x *Action) Reset() { *x = Action{} if protoimpl.UnsafeEnabled { - mi := &file_firewall_proto_msgTypes[2] + mi := &file_firewall_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -373,7 +493,7 @@ func (x *Action) String() string { func (*Action) ProtoMessage() {} func (x *Action) ProtoReflect() protoreflect.Message { - mi := &file_firewall_proto_msgTypes[2] + mi := &file_firewall_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -386,7 +506,7 @@ func (x *Action) ProtoReflect() protoreflect.Message { // Deprecated: Use Action.ProtoReflect.Descriptor instead. func (*Action) Descriptor() ([]byte, []int) { - return file_firewall_proto_rawDescGZIP(), []int{2} + return file_firewall_proto_rawDescGZIP(), []int{4} } func (x *Action) GetActorName() string { @@ -470,81 +590,98 @@ var File_firewall_proto protoreflect.FileDescriptor var file_firewall_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x9f, 0x03, 0x0a, 0x12, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, - 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x4e, 0x75, - 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, - 0x01, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x12, 0x27, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0c, 0x65, 0x6e, - 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x8c, 0x01, 0x0a, 0x13, 0x4c, - 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x0a, 0x11, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, 0x41, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, - 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, - 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x64, 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x70, - 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x72, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x70, 0x63, - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x72, 0x70, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4a, 0x73, 0x6f, - 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x2a, 0x54, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, - 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, - 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x32, 0x52, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, - 0x6c, 0x6c, 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, - 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, - 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, - 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x06, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x22, 0x78, 0x0a, 0x1b, 0x50, 0x72, 0x69, 0x76, + 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x65, 0x61, 0x6c, 0x5f, + 0x74, 0x6f, 0x5f, 0x70, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x6f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6e, 0x70, + 0x75, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, + 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x9f, 0x03, 0x0a, 0x12, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x6d, 0x61, 0x78, + 0x4e, 0x75, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, + 0x76, 0x65, 0x72, 0x73, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x42, + 0x02, 0x30, 0x01, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x27, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x0c, + 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x8c, 0x01, 0x0a, + 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2a, + 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, + 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x65, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x69, 0x67, + 0x67, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x69, 0x67, 0x67, + 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, + 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x75, 0x72, 0x65, 0x64, 0x4a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x70, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, + 0x70, 0x63, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x70, 0x63, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4a, + 0x73, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x30, 0x01, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x29, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x49, 0x64, 0x2a, 0x54, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, + 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x32, 0xb5, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x72, + 0x65, 0x77, 0x61, 0x6c, 0x6c, 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, + 0x14, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x69, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, + 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, + 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, + 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -560,21 +697,25 @@ func file_firewall_proto_rawDescGZIP() []byte { } var file_firewall_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_firewall_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_firewall_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_firewall_proto_goTypes = []interface{}{ - (ActionState)(0), // 0: litrpc.ActionState - (*ListActionsRequest)(nil), // 1: litrpc.ListActionsRequest - (*ListActionsResponse)(nil), // 2: litrpc.ListActionsResponse - (*Action)(nil), // 3: litrpc.Action + (ActionState)(0), // 0: litrpc.ActionState + (*PrivacyMapConversionRequest)(nil), // 1: litrpc.PrivacyMapConversionRequest + (*PrivacyMapConversionResponse)(nil), // 2: litrpc.PrivacyMapConversionResponse + (*ListActionsRequest)(nil), // 3: litrpc.ListActionsRequest + (*ListActionsResponse)(nil), // 4: litrpc.ListActionsResponse + (*Action)(nil), // 5: litrpc.Action } var file_firewall_proto_depIdxs = []int32{ 0, // 0: litrpc.ListActionsRequest.state:type_name -> litrpc.ActionState - 3, // 1: litrpc.ListActionsResponse.actions:type_name -> litrpc.Action + 5, // 1: litrpc.ListActionsResponse.actions:type_name -> litrpc.Action 0, // 2: litrpc.Action.state:type_name -> litrpc.ActionState - 1, // 3: litrpc.Firewall.ListActions:input_type -> litrpc.ListActionsRequest - 2, // 4: litrpc.Firewall.ListActions:output_type -> litrpc.ListActionsResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type + 3, // 3: litrpc.Firewall.ListActions:input_type -> litrpc.ListActionsRequest + 1, // 4: litrpc.Firewall.PrivacyMapConversion:input_type -> litrpc.PrivacyMapConversionRequest + 4, // 5: litrpc.Firewall.ListActions:output_type -> litrpc.ListActionsResponse + 2, // 6: litrpc.Firewall.PrivacyMapConversion:output_type -> litrpc.PrivacyMapConversionResponse + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -587,7 +728,7 @@ func file_firewall_proto_init() { } if !protoimpl.UnsafeEnabled { file_firewall_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListActionsRequest); i { + switch v := v.(*PrivacyMapConversionRequest); i { case 0: return &v.state case 1: @@ -599,7 +740,7 @@ func file_firewall_proto_init() { } } file_firewall_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListActionsResponse); i { + switch v := v.(*PrivacyMapConversionResponse); i { case 0: return &v.state case 1: @@ -611,6 +752,30 @@ func file_firewall_proto_init() { } } file_firewall_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListActionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_firewall_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListActionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_firewall_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Action); i { case 0: return &v.state @@ -629,7 +794,7 @@ func file_firewall_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_firewall_proto_rawDesc, NumEnums: 1, - NumMessages: 3, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/litrpc/firewall.pb.json.go b/litrpc/firewall.pb.json.go index c698494ba..faafa22e7 100644 --- a/litrpc/firewall.pb.json.go +++ b/litrpc/firewall.pb.json.go @@ -45,4 +45,29 @@ func RegisterFirewallJSONCallbacks(registry map[string]func(ctx context.Context, } callback(string(respBytes), nil) } + + registry["litrpc.Firewall.PrivacyMapConversion"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &PrivacyMapConversionRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewFirewallClient(conn) + resp, err := client.PrivacyMapConversion(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } } diff --git a/litrpc/firewall.proto b/litrpc/firewall.proto index 8f7f5a560..8c5b18e88 100644 --- a/litrpc/firewall.proto +++ b/litrpc/firewall.proto @@ -6,6 +6,34 @@ option go_package = "github.com/lightninglabs/lightning-terminal/litrpc"; service Firewall { rpc ListActions (ListActionsRequest) returns (ListActionsResponse); + rpc PrivacyMapConversion (PrivacyMapConversionRequest) + returns (PrivacyMapConversionResponse); +} + +message PrivacyMapConversionRequest { + /* + If set to true, then the input string will be taken as the real value and + the response will the the pseudo value it if exists. Otherwise, the input + string will be assumed to be the pseudo value. + */ + bool real_to_pseudo = 1; + + /* + The session ID under which to search for the real-pseudo pair. + */ + bytes session_id = 2; + + /* + The input to be converted into the real or pseudo value. + */ + string input = 3; +} + +message PrivacyMapConversionResponse { + /* + The resulting real or pseudo output. + */ + string output = 1; } message ListActionsRequest { diff --git a/litrpc/firewall_grpc.pb.go b/litrpc/firewall_grpc.pb.go index 45d1d98c5..78c05e5b8 100644 --- a/litrpc/firewall_grpc.pb.go +++ b/litrpc/firewall_grpc.pb.go @@ -19,6 +19,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type FirewallClient interface { ListActions(ctx context.Context, in *ListActionsRequest, opts ...grpc.CallOption) (*ListActionsResponse, error) + PrivacyMapConversion(ctx context.Context, in *PrivacyMapConversionRequest, opts ...grpc.CallOption) (*PrivacyMapConversionResponse, error) } type firewallClient struct { @@ -38,11 +39,21 @@ func (c *firewallClient) ListActions(ctx context.Context, in *ListActionsRequest return out, nil } +func (c *firewallClient) PrivacyMapConversion(ctx context.Context, in *PrivacyMapConversionRequest, opts ...grpc.CallOption) (*PrivacyMapConversionResponse, error) { + out := new(PrivacyMapConversionResponse) + err := c.cc.Invoke(ctx, "/litrpc.Firewall/PrivacyMapConversion", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // FirewallServer is the server API for Firewall service. // All implementations must embed UnimplementedFirewallServer // for forward compatibility type FirewallServer interface { ListActions(context.Context, *ListActionsRequest) (*ListActionsResponse, error) + PrivacyMapConversion(context.Context, *PrivacyMapConversionRequest) (*PrivacyMapConversionResponse, error) mustEmbedUnimplementedFirewallServer() } @@ -53,6 +64,9 @@ type UnimplementedFirewallServer struct { func (UnimplementedFirewallServer) ListActions(context.Context, *ListActionsRequest) (*ListActionsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListActions not implemented") } +func (UnimplementedFirewallServer) PrivacyMapConversion(context.Context, *PrivacyMapConversionRequest) (*PrivacyMapConversionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PrivacyMapConversion not implemented") +} func (UnimplementedFirewallServer) mustEmbedUnimplementedFirewallServer() {} // UnsafeFirewallServer may be embedded to opt out of forward compatibility for this service. @@ -84,6 +98,24 @@ func _Firewall_ListActions_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Firewall_PrivacyMapConversion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PrivacyMapConversionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FirewallServer).PrivacyMapConversion(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/litrpc.Firewall/PrivacyMapConversion", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FirewallServer).PrivacyMapConversion(ctx, req.(*PrivacyMapConversionRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Firewall_ServiceDesc is the grpc.ServiceDesc for Firewall service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -95,6 +127,10 @@ var Firewall_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListActions", Handler: _Firewall_ListActions_Handler, }, + { + MethodName: "PrivacyMapConversion", + Handler: _Firewall_PrivacyMapConversion_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "firewall.proto", diff --git a/litrpc/lit-accounts.pb.go b/litrpc/lit-accounts.pb.go index ccc5371a1..5d203eaae 100644 --- a/litrpc/lit-accounts.pb.go +++ b/litrpc/lit-accounts.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.28.1 // protoc v3.6.1 // source: lit-accounts.proto diff --git a/perms/permissions.go b/perms/permissions.go index 4b4e80cd7..9fb8727f4 100644 --- a/perms/permissions.go +++ b/perms/permissions.go @@ -82,6 +82,10 @@ var ( Entity: "autopilot", Action: "write", }}, + "/litrpc.Firewall/PrivacyMapConversion": {{ + Entity: "privacymap", + Action: "read", + }}, } // whiteListedLNDMethods is a map of all lnd RPC methods that don't diff --git a/session_rpcserver.go b/session_rpcserver.go index a59a51f8b..569d8f678 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -581,6 +581,36 @@ func (s *sessionRpcServer) RevokeSession(ctx context.Context, return &litrpc.RevokeSessionResponse{}, nil } +func (s *sessionRpcServer) PrivacyMapConversion(_ context.Context, + req *litrpc.PrivacyMapConversionRequest) ( + *litrpc.PrivacyMapConversionResponse, error) { + + sessionID, err := session.IDFromBytes(req.SessionId) + if err != nil { + return nil, err + } + + var res string + privMap := s.cfg.privMap(sessionID) + err = privMap.View(func(tx firewalldb.PrivacyMapTx) error { + var err error + if req.RealToPseudo { + res, err = tx.RealToPseudo(req.Input) + return err + } + + res, err = tx.PseudoToReal(req.Input) + return err + }) + if err != nil { + return nil, err + } + + return &litrpc.PrivacyMapConversionResponse{ + Output: res, + }, nil +} + // ListActions lists all actions attempted on the Litd server. func (s *sessionRpcServer) ListActions(_ context.Context, req *litrpc.ListActionsRequest) (*litrpc.ListActionsResponse, error) { diff --git a/terminal.go b/terminal.go index 58ca075db..d9ca88f6d 100644 --- a/terminal.go +++ b/terminal.go @@ -323,6 +323,7 @@ func (g *LightningTerminal) Run() error { actionsDB: g.firewallDB, autopilot: g.autopilotClient, ruleMgrs: g.ruleMgrs, + privMap: g.firewallDB.PrivacyDB, }) if err != nil { return fmt.Errorf("could not create new session rpc "+ From e793268de7c330133d904d470042caed6bdba5c2 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 15 Sep 2022 16:53:54 +0200 Subject: [PATCH 37/47] itest: add mock autopilot server to network harness --- itest/network_harness.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/itest/network_harness.go b/itest/network_harness.go index 6ff01ce6e..f529c23ba 100644 --- a/itest/network_harness.go +++ b/itest/network_harness.go @@ -18,6 +18,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/lightning-terminal/autopilotserver/mock" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -55,6 +56,8 @@ type NetworkHarness struct { nodesByPub map[string]*HarnessNode + autopilotServer *mock.Server + // Alice and Bob are the initial seeder nodes that are automatically // created to be the initial participants of the test network. Alice *HarnessNode @@ -130,6 +133,10 @@ func (n *NetworkHarness) SetUp(t *testing.T, err := n.server.start() require.NoError(t, err) + // Start a mock autopilot server. + n.autopilotServer = mock.NewServer() + require.NoError(t, n.autopilotServer.Start()) + // Start the initial seeder nodes within the test network, then connect // their respective RPC clients. eg := errgroup.Group{} @@ -260,6 +267,8 @@ func (n *NetworkHarness) TearDown() error { func (n *NetworkHarness) Stop() { close(n.lndErrorChan) close(n.quit) + + n.autopilotServer.Stop() } // NewNode initializes a new HarnessNode. @@ -271,6 +280,11 @@ func (n *NetworkHarness) NewNode(t *testing.T, name string, extraArgs []string, fmt.Sprintf("--loop.server.tlspath=%s", n.server.certFile), fmt.Sprintf("--pool.auctionserver=%s", n.server.serverHost), fmt.Sprintf("--pool.tlspathauctserver=%s", n.server.certFile), + "--autopilot.insecure", + fmt.Sprintf( + "--autopilot.address=localhost:%d", + n.autopilotServer.GetPort(), + ), } return n.newNode( From b348c9d7080f5dd08b55c8ad34d79da5578b6f6c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 15 Sep 2022 16:57:56 +0200 Subject: [PATCH 38/47] itest: add firewall rule itests --- itest/litd_accounts_test.go | 2 +- itest/litd_firewall_test.go | 1234 ++++++++++++++++++++++++++++ itest/litd_mode_integrated_test.go | 8 +- itest/litd_test_list_on_test.go | 4 + 4 files changed, 1243 insertions(+), 5 deletions(-) create mode 100644 itest/litd_firewall_test.go diff --git a/itest/litd_accounts_test.go b/itest/litd_accounts_test.go index e0455c5d2..365f6a7a0 100644 --- a/itest/litd_accounts_test.go +++ b/itest/litd_accounts_test.go @@ -190,7 +190,7 @@ func testAccountRestrictionsLNC(ctxm context.Context, t *harnessTest, ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() - rawLNCConn, err := connectMailbox(ctxt, connectPhrase) + rawLNCConn, err := connectMailboxWithPairingPhrase(ctxt, connectPhrase) require.NoError(t.t, err) lightningClient := lnrpc.NewLightningClient(rawLNCConn) diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go new file mode 100644 index 000000000..780f70141 --- /dev/null +++ b/itest/litd_firewall_test.go @@ -0,0 +1,1234 @@ +package itest + +import ( + "context" + "encoding/hex" + "fmt" + "io/ioutil" + "strconv" + "strings" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightninglabs/lightning-node-connect/mailbox" + "github.com/lightninglabs/lightning-terminal/autopilotserver/mock" + "github.com/lightninglabs/lightning-terminal/firewall" + "github.com/lightninglabs/lightning-terminal/litrpc" + "github.com/lightninglabs/lightning-terminal/rules" + "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +const ( + // HeaderMacaroon is the HTTP header field name that is used to send + // the macaroon. + HeaderMacaroon = "Macaroon" +) + +var ( + rateLimit = &litrpc.RuleValue_RateLimit{ + RateLimit: &litrpc.RateLimit{ + ReadLimit: &litrpc.Rate{ + Iterations: 2, + NumHours: 1, + }, + WriteLimit: &litrpc.Rate{ + Iterations: 1, + NumHours: 1, + }, + }, + } + + policyBounds = &litrpc.RuleValue_ChanPolicyBounds{ + ChanPolicyBounds: &litrpc.ChannelPolicyBounds{ + MinBaseMsat: 100, + MaxBaseMsat: 1000, + MinRatePpm: 5000000, + MaxRatePpm: 10000000, + MinCltvDelta: 18, + MaxCltvDelta: 40, + MinHtlcMsat: 1000, + MaxHtlcMsat: 10000000, + }, + } + + historyLimit1 = &litrpc.RuleValue_HistoryLimit{ + HistoryLimit: &litrpc.HistoryLimit{ + Duration: uint64(time.Hour.Seconds() * 24 * 2), + }, + } + + historyLimit2 = &litrpc.RuleValue_HistoryLimit{ + HistoryLimit: &litrpc.HistoryLimit{ + StartTime: uint64( + time.Now().Add(-time.Hour * 24 * 3).Unix(), + ), + }, + } + + sendToSelf = &litrpc.RuleValue_SendToSelf{ + SendToSelf: &litrpc.SendToSelf{}, + } + + offChainBudget = &litrpc.RuleValue_OffChainBudget{ + OffChainBudget: &litrpc.OffChainBudget{ + MaxAmtMsat: 1200000000, + MaxFeesMsat: 200000, + }, + } + + chanPolicyBoundsRule = &mock.RuleRanges{ + Default: &rules.ChanPolicyBounds{ + MinBaseMsat: 0, + MaxBaseMsat: 10, + MinRatePPM: 1, + MaxRatePPM: 10, + MinCLTVDelta: 18, + MaxCLTVDelta: 18, + MinHtlcMsat: 1000, + MaxHtlcMsat: 10000, + }, + MinVal: &rules.ChanPolicyBounds{ + MinBaseMsat: 0, + MaxBaseMsat: 0, + MinRatePPM: 0, + MaxRatePPM: 0, + MinCLTVDelta: 18, + MaxCLTVDelta: 20, + MinHtlcMsat: 100, + MaxHtlcMsat: 1000, + }, + MaxVal: &rules.ChanPolicyBounds{ + MinBaseMsat: 1000, + MaxBaseMsat: 1000, + MinRatePPM: 5000000, + MaxRatePPM: 100000000, + MinCLTVDelta: 40, + MaxCLTVDelta: 60, + MinHtlcMsat: 100000, + MaxHtlcMsat: 1000000000000, + }, + } + + historyLimitRule = &mock.RuleRanges{ + Default: &rules.HistoryLimit{ + StartDate: time.Unix(0, 0), + }, + MinVal: &rules.HistoryLimit{ + Duration: time.Hour * 24, + }, + MaxVal: &rules.HistoryLimit{}, + } +) + +// testFWRateLimitAndPrivacyMapper tests that an Autopilot session is forced to +// adhere to the rate limits applied to the features of a session. Along the +// way, the privacy mapper is also tested. +func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // Some very basic functionality tests to make sure lnd is working fine + // in integrated mode. + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + + // We expect a non-empty alias (truncated node ID) to be returned. + resp, err := net.Alice.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + require.NoError(t.t, err) + require.NotEmpty(t.t, resp.Alias) + require.Contains(t.t, resp.Alias, "0") + + // Open a channel between Alice and Bob so that we have something to + // query later. + channelOp := openChannelAndAssert( + t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelOp, true) + + // We extract the txid of the channel so that we can use it later to + // check that the autopilot's actions successfully completed and to + // check that the txid that the autopilot server sees is not the same + // as this one. + realTxidBytes, err := getChanPointFundingTxid(channelOp) + require.NoError(t.t, err) + realTxid, err := chainhash.NewHash(realTxidBytes) + require.NoError(t.t, err) + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.TLSCertPath) + require.NoError(t.t, err) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Add a new Autopilot session that subscribes to both a "HealthCheck", + // and an "AutoFees" feature. Apply rate limits to the two features. + // This call is expected to also result in Litd registering this session + // with the mock autopilot server. + sessResp, err := litClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "HealthCheck": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.RateLimitName: { + Value: rateLimit, + }, + }, + }, + }, + "AutoFees": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.RateLimitName: { + Value: rateLimit, + }, + }, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + // From the response, we can extract Lit's local public key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + lndConn := lnrpc.NewLightningClient(pilotConn) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the HealthCheck feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "HealthCheck", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // Make first read request. Also check that the public key that the + // autopilot server receives from this request is not the same as + // Alice's actual public key. This is due to the actions of the privacy + // mapper. + getInfoReq, err := lndConn.GetInfo( + ctx, &lnrpc.GetInfoRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.NotEqual( + t.t, hex.EncodeToString(net.Alice.PubKey[:]), + getInfoReq.IdentityPubkey, + ) + + // Make second read request. Also check that the privacy mapper is + // consistent with returning the same pseudo pub key. + getInfoReq2, err := lndConn.GetInfo( + ctx, &lnrpc.GetInfoRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Equal( + t.t, getInfoReq.IdentityPubkey, getInfoReq2.IdentityPubkey, + ) + + // The third read request should fail due to exceeding the read limit. + _, err = lndConn.GetInfo(ctx, &lnrpc.GetInfoRequest{}, caveatCreds) + assertStatusErr(t.t, err, codes.ResourceExhausted) + + // The autopilot should still be able to make calls for the AutoFees + // feature though. + metaInfo = &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees", + } + caveat, err = metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds = metaDataInjector.addCaveat(caveat) + + // GetInfo should not be allowed for the AutoFees feature. + _, err = lndConn.GetInfo(ctx, &lnrpc.GetInfoRequest{}, caveatCreds) + require.Error(t.t, err) + + // Make a valid read request for the feature. + channelsResp, err := lndConn.ListChannels( + ctx, &lnrpc.ListChannelsRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Len(t.t, channelsResp.Channels, 1) + + txid, index, err := decodeChannelPoint( + channelsResp.Channels[0].ChannelPoint, + ) + require.NoError(t.t, err) + + // Make sure that the txid returned by the call for Alice's channel is + // not the same as the real txid of the channel. + require.NotEqual(t.t, txid, realTxid.String()) + + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: txid, + }, + OutputIndex: index, + } + + // As the autopilot server, update the fees of the channel. + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + require.NoError(t.t, err) + + // Using the real txid, from the PoV of Alice, lets query this channel + // and make sure that the update successfully happened. + feeResp, err := net.Alice.FeeReport(ctx, &lnrpc.FeeReportRequest{}) + require.NoError(t.t, err) + require.Len(t.t, feeResp.ChannelFees, 1) + + txid2, _, err := decodeChannelPoint(feeResp.ChannelFees[0].ChannelPoint) + require.NoError(t.t, err) + require.Equal(t.t, realTxid.String(), txid2) + require.Equal(t.t, float64(8), feeResp.ChannelFees[0].FeeRate) + + // Now we will check the same thing but from the PoV of the autopilot + // server using the fake txid. + feeResp, err = lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Len(t.t, feeResp.ChannelFees, 1) + + txid3, _, err := decodeChannelPoint(feeResp.ChannelFees[0].ChannelPoint) + require.NoError(t.t, err) + require.Equal(t.t, txid, txid3) + require.Equal(t.t, float64(8), feeResp.ChannelFees[0].FeeRate) + + // Check that any more read or write requests from the autopilot server + // are disallowed due to the read and write rate limits being reached. + _, err = lndConn.FeeReport(ctx, &lnrpc.FeeReportRequest{}, caveatCreds) + assertStatusErr(t.t, err, codes.ResourceExhausted) + + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 10, + FeeRate: 4, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) +} + +// assertStatusErr asserts that the given error contains the given status code. +func assertStatusErr(t *testing.T, err error, code codes.Code) { + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), code.String())) +} + +// testFirewallRules tests that the various firewall rules are enforced +// correctly. +func testFirewallRules(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // Some very basic functionality tests to make sure lnd is working fine + // in integrated mode. + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) + + // We expect a non-empty alias (truncated node ID) to be returned. + resp, err := net.Alice.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + require.NoError(t.t, err) + require.NotEmpty(t.t, resp.Alias) + require.Contains(t.t, resp.Alias, "0") + + // Open a channel between Alice and Bob so that we have something to + // query later. + channelOp := openChannelAndAssert( + t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelOp, true) + + t.t.Run("history limit rule", func(_ *testing.T) { + testHistoryLimitRule(net, t) + }) + + t.t.Run("channel policy bounds rule", func(_ *testing.T) { + testChanPolicyBoundsRule(net, t) + }) + + t.t.Run("peer and channel restrict rules", func(_ *testing.T) { + testPeerAndChannelRestrictRules(net, t) + }) +} + +// testHistoryLimitRule tests that the autopilot server is forced to adhere to +// the history-limit rule. +func testHistoryLimitRule(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.TLSCertPath) + require.NoError(t.t, err) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Now we will override the autopilots features set so that we can + // just focus on the fee bounds rule for now. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "AutoFees": { + Description: "manages your channel fees", + Rules: map[string]*mock.RuleRanges{ + rules.HistoryLimitName: historyLimitRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/ForwardingHistory": {{ + Entity: "offchain", + Action: "read", + }}, + }, + }, + "AutoFees2": { + Description: "manages your channel fees another way!", + Rules: map[string]*mock.RuleRanges{ + rules.HistoryLimitName: historyLimitRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/ForwardingHistory": {{ + Entity: "offchain", + Action: "read", + }}, + }, + }, + }) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Add a new Autopilot session that subscribes to the "AutoFees" feature + // and apply a history limit that uses a Duration to it. Then we will + // also add an "AutoFees2" feature and apply a history limit that uses + // a Start-data to it. + sessResp, err := litClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "AutoFees": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.HistoryLimitName: { + Value: historyLimit1, + }, + }, + }, + }, + "AutoFees2": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.HistoryLimitName: { + Value: historyLimit2, + }, + }, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + // From the response, we can extract Lit's local public key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + + lndConn := lnrpc.NewLightningClient(pilotConn) + + // First, we will test the "AutoFees" feature which uses a duration to + // specify a history limit. The duration specified is 48 hours and so + // the autopilot should not be able to query for more data more than 48 + // hours old. + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the AutoFees feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // If the autopilot queries for data starting from 72 hours ago, the + // call should fail. + _, err = lndConn.ForwardingHistory( + ctx, &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64( + time.Now().Add(-time.Hour * 24 * 3).Unix(), + ), + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + + // If the autopilot queries for data starting from somewhere within the + // last 48 hours, the call should succeed. + _, err = lndConn.ForwardingHistory( + ctx, &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64( + time.Now().Add(-time.Hour * 24).Unix(), + ), + }, caveatCreds, + ) + require.NoError(t.t, err) + + // Now, we will test the "AutoFees2" feature which uses a start date to + // specify a history limit. The start date specified is now minus 72 + // hours and so the autopilot should not be able to query for more data + // more than 72 hours old. + metaInfo.Feature = "AutoFees2" + caveat, err = metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds = metaDataInjector.addCaveat(caveat) + + // If the autopilot queries for data starting from 96 hours ago, the + // call should fail. + _, err = lndConn.ForwardingHistory( + ctx, &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64( + time.Now().Add(-time.Hour * 24 * 4).Unix(), + ), + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + + // If the autopilot queries for data starting from somewhere within the + // last 72 hours, the call should succeed. + _, err = lndConn.ForwardingHistory( + ctx, &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64( + time.Now().Add(-time.Hour * 24 * 2).Unix(), + ), + }, caveatCreds, + ) + require.NoError(t.t, err) +} + +// testChanPolicyBoundsRule tests that the autopilot server is forced to adhere +// to the fee-bounds rule. +func testChanPolicyBoundsRule(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.TLSCertPath) + require.NoError(t.t, err) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Now we will override the autopilots features set so that we can + // just focus on the fee bounds rule for now. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "AutoFees": { + Description: "manages your channel fees", + Rules: map[string]*mock.RuleRanges{ + rules.ChanPolicyBoundsName: chanPolicyBoundsRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/UpdateChannelPolicy": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/FeeReport": {{ + Entity: "offchain", + Action: "read", + }}, + }, + }, + }) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Add a new Autopilot session that subscribes to the "AutoFees" feature + // and apply a rate limit to it. + sessResp, err := litClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "AutoFees": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.ChanPolicyBoundsName: { + Value: policyBounds, + }, + }, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + // From the response, we can extract Lit's local public key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + lndConn := lnrpc.NewLightningClient(pilotConn) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the HealthCheck feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // First, get the current fee rates of the litd node. + var chanFees *lnrpc.ChannelFeeReport + assertFees := func(expectedFeeRate float64, expectedBase int64) { + feeResp, err := lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Len(t.t, feeResp.ChannelFees, 1) + require.Equal( + t.t, expectedFeeRate, feeResp.ChannelFees[0].FeeRate, + ) + require.Equal( + t.t, expectedBase, feeResp.ChannelFees[0].BaseFeeMsat, + ) + chanFees = feeResp.ChannelFees[0] + } + + assertFees(1e-06, 1000) + + txid, index, err := decodeChannelPoint(chanFees.ChannelPoint) + require.NoError(t.t, err) + + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: txid, + }, + OutputIndex: index, + } + + // Now try to update the fees by violating the minimum allowed base + // fee rate. This should fail. + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 10, + FeeRate: 4, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + assertFees(1e-06, 1000) + + // Base fee too high. + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 60000, + FeeRate: 4, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + assertFees(1e-06, 1000) + + // Fee rate too low. + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 500, + FeeRate: 0, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 10, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + assertFees(1e-06, 1000) + + // Fee rate too high. + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 500, + FeeRate: 100, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + assertFees(1e-06, 1000) + + // Valid fee change! + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 500, + FeeRate: 7, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + require.NoError(t.t, err) + assertFees(7, 500) +} + +func testPeerAndChannelRestrictRules(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + charlie, err := net.NewNode(t.t, "Charlie", nil, false, true) + require.NoError(t.t, err) + defer shutdownAndAssert(net, t, charlie) + + net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, charlie) + net.EnsureConnected(t.t, net.Alice, charlie) + + // Open another channel between Alice and Bob. + channelAB2Op := openChannelAndAssert( + t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, true) + + // Open two channels between Alice and Charlie. + channelAC1Op := openChannelAndAssert( + t, net, net.Alice, charlie, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelAC1Op, true) + + channelAC2Op := openChannelAndAssert( + t, net, net.Alice, charlie, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelAC2Op, true) + + // List Alice's channels so that we can extract the channel points + // for the four channels. + chans, err := net.Alice.ListChannels(ctx, &lnrpc.ListChannelsRequest{}) + require.NoError(t.t, err) + require.Len(t.t, chans.Channels, 4) + + var chanABPoints, chanACPoints []string + var chanToRestrict uint64 + for _, c := range chans.Channels { + if c.RemotePubkey == hex.EncodeToString(net.Bob.PubKey[:]) { + chanABPoints = append(chanABPoints, c.ChannelPoint) + continue + } + chanACPoints = append(chanACPoints, c.ChannelPoint) + chanToRestrict = c.ChanId + } + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.TLSCertPath) + require.NoError(t.t, err) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Now we will override the autopilots features set so that we can + // just focus on the peer and channel restriction rules for now. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "AutoFees": { + Description: "manages your channel fees", + Rules: map[string]*mock.RuleRanges{ + rules.PeersRestrictName: { + Default: &rules.PeerRestrict{}, + MinVal: &rules.PeerRestrict{}, + MaxVal: &rules.PeerRestrict{}, + }, + rules.ChannelRestrictName: { + Default: &rules.ChannelRestrict{}, + MinVal: &rules.ChannelRestrict{}, + MaxVal: &rules.ChannelRestrict{}, + }, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/UpdateChannelPolicy": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/FeeReport": {{ + Entity: "offchain", + Action: "read", + }}, + }, + }, + }) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litCAutopilotClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litCAutopilotClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Construct a peer-restriction list using Bob's pub key. + bobKey := hex.EncodeToString(net.Bob.PubKey[:]) + peerRestrict := &litrpc.RuleValue_PeerRestrict{ + PeerRestrict: &litrpc.PeerRestrict{ + PeerIds: []string{bobKey}, + }, + } + + // Next, construct a channel-restriction list using one of the channels + channelRestrict := &litrpc.RuleValue_ChannelRestrict{ + ChannelRestrict: &litrpc.ChannelRestrict{ + ChannelIds: []uint64{chanToRestrict}, + }, + } + + // Add a new Autopilot session that subscribes to the "AutoFees" feature + // and apply a peer restriction list to it. + sessResp, err := litCAutopilotClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "AutoFees": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.PeersRestrictName: { + Value: peerRestrict, + }, + rules.ChannelRestrictName: { + Value: channelRestrict, + }, + }, + }, + }, + }, + }, + ) + require.NoError(t.t, err) + + // From the response, we can extract Lit's local public key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + lndConn := lnrpc.NewLightningClient(pilotConn) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the HealthCheck feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // From the PoV of the Autopilot server, we do a quick FeeReport call. + // This will force the Privacy Mapper on Alice's node to create the + // real-pseudo pairs for all her channel id's and points. We do this + // so that we can know which channels we are referring to when we make + // calls from the autopilot later on in this test. + feeReport, err := lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Len(t.t, feeReport.ChannelFees, 4) + + // Query Alice's privacy mapper to see which pseudo channel ID the + // autopilot should use when it tries to update one of the channels + // that Alice has with Bob. + litClient := litrpc.NewFirewallClient(rawConn) + privMapResp, err := litClient.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + SessionId: sessResp.Session.Id, + RealToPseudo: true, + Input: chanABPoints[0], + }, + ) + require.NoError(t.t, err) + + pseudoChanPoint := privMapResp.Output + pseudoTxid, pseudoIndex, err := decodeChannelPoint(pseudoChanPoint) + require.NoError(t.t, err) + + // Make sure that this channel point did in fact appear in the list + // provided to the autopilot. + var found bool + for _, c := range feeReport.ChannelFees { + if pseudoChanPoint == c.ChannelPoint { + found = true + } + } + require.True(t.t, found) + + // Now, from the autopilot's PoV, try to update the fees on this + // channel. It should fail due to the fact that the channel peer, Bob, + // is in the peer-restriction rule that Alice applied to the session. + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: pseudoTxid, + }, + OutputIndex: pseudoIndex, + } + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + require.ErrorContains(t.t, err, "illegal action on peer in peer "+ + "restriction list") + + // We now do the same thing for the other channel that Alice has with + // Bob. This should also fail due to Bob being on the peer-restrict + // list. + privMapResp, err = litClient.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + SessionId: sessResp.Session.Id, + RealToPseudo: true, + Input: chanABPoints[1], + }, + ) + require.NoError(t.t, err) + + pseudoChanPoint = privMapResp.Output + pseudoTxid, pseudoIndex, err = decodeChannelPoint(pseudoChanPoint) + require.NoError(t.t, err) + + // Now, from the autopilot's PoV, try to update the fees on this + // channel. It should fail due to the fact that the channel peer, Bob, + // is in the peer-restriction rule that Alice applied to the session. + chanPoint = &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: pseudoTxid, + }, + OutputIndex: pseudoIndex, + } + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + require.ErrorContains(t.t, err, "illegal action on peer in peer "+ + "restriction list") + + // Again we query Alice's privacy mapper to see which pseudo channel ID + // the autopilot should use for the channel she has with Charlie. + // We first do this for the channel that is in the channel restrict + // list. + privMapResp, err = litClient.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + SessionId: sessResp.Session.Id, + RealToPseudo: true, + Input: chanACPoints[1], + }, + ) + require.NoError(t.t, err) + + pseudoChanPoint = privMapResp.Output + pseudoTxid, pseudoIndex, err = decodeChannelPoint(pseudoChanPoint) + require.NoError(t.t, err) + + // The call to update the channel policy should fail due to this + // channel being in the channel-restrict list. + chanPoint = &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: pseudoTxid, + }, + OutputIndex: pseudoIndex, + } + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) + require.ErrorContains(t.t, err, "illegal action on channel in channel "+ + "restriction list") + + // Finally, we repeat the test on the other channel that Alice has + // with Charlie (the channel _not_ in the channel-restrict list). + privMapResp, err = litClient.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + SessionId: sessResp.Session.Id, + RealToPseudo: true, + Input: chanACPoints[0], + }, + ) + require.NoError(t.t, err) + + pseudoChanPoint = privMapResp.Output + pseudoTxid, pseudoIndex, err = decodeChannelPoint(pseudoChanPoint) + require.NoError(t.t, err) + + // The call to update the channel policy should succeed. + chanPoint = &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: pseudoTxid, + }, + OutputIndex: pseudoIndex, + } + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds, + ) + require.NoError(t.t, err) +} + +// connectMailboxWithRemoteKey tries to establish a connection through LNC using +// the given local and remote keys and the test mailbox server. +func connectMailboxWithRemoteKey(ctx context.Context, + localKey *btcec.PrivateKey, remoteKey *btcec.PublicKey) ( + *grpc.ClientConn, *metadataInjector, error) { + + ecdh := &keychain.PrivKeyECDH{PrivKey: localKey} + connData := mailbox.NewConnData(ecdh, remoteKey, nil, nil, nil, nil) + + transportConn, err := mailbox.NewClient(ctx, connData) + if err != nil { + return nil, nil, err + } + + noiseConn := mailbox.NewNoiseGrpcConn(connData) + + dialOpts := []grpc.DialOption{ + grpc.WithContextDialer(transportConn.Dial), + grpc.WithTransportCredentials(noiseConn), + grpc.WithBlock(), + } + + conn, err := grpc.DialContext(ctx, mailboxServerAddr, dialOpts...) + return conn, &metadataInjector{originalCredentials: noiseConn}, err +} + +type metadataInjector struct { + originalCredentials credentials.PerRPCCredentials +} + +func (m *metadataInjector) addCaveat(caveat string) grpc.PerRPCCredsCallOption { + return grpc.PerRPCCredsCallOption{ + Creds: &caveatCredentials{ + originalCredentials: m.originalCredentials, + caveat: caveat, + }, + } +} + +type caveatCredentials struct { + originalCredentials credentials.PerRPCCredentials + caveat string +} + +func (c *caveatCredentials) GetRequestMetadata(ctx context.Context, + uri ...string) (map[string]string, error) { + + metadata, err := c.originalCredentials.GetRequestMetadata(ctx, uri...) + if err != nil { + return nil, err + } + + macHex, ok := metadata[HeaderMacaroon] + if !ok || len(macHex) == 0 { + return metadata, nil + } + + mac, err := session.ParseMacaroon(macHex) + if err != nil { + return nil, err + } + + if err := mac.AddFirstPartyCaveat([]byte(c.caveat)); err != nil { + return nil, err + } + + macBytes, err := mac.MarshalBinary() + if err != nil { + return nil, err + } + + metadata[HeaderMacaroon] = hex.EncodeToString(macBytes) + + return metadata, nil +} + +// RequireTransportSecurity indicates whether the credentials requires +// transport security. +func (c *caveatCredentials) RequireTransportSecurity() bool { + return true +} + +func decodeChannelPoint(cp string) (string, uint32, error) { + parts := strings.Split(cp, ":") + if len(parts) != 2 { + return "", 0, fmt.Errorf("bad channel point encoding") + } + + index, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil { + return "", 0, err + } + + return parts[0], uint32(index), nil +} diff --git a/itest/litd_mode_integrated_test.go b/itest/litd_mode_integrated_test.go index 09d6f3469..cc7ac75cf 100644 --- a/itest/litd_mode_integrated_test.go +++ b/itest/litd_mode_integrated_test.go @@ -516,7 +516,7 @@ func setUpLNCConn(ctx context.Context, t *testing.T, hostPort, tlsCertPath, sessResp.Session.PairingSecretMnemonic, " ", ) - rawLNCConn, err := connectMailbox(ctx, connectPhrase) + rawLNCConn, err := connectMailboxWithPairingPhrase(ctx, connectPhrase) require.NoError(t, err) return rawLNCConn @@ -876,9 +876,9 @@ func getServerCertificates(hostPort string) ([]*x509.Certificate, error) { return conn.ConnectionState().PeerCertificates, nil } -// connectMailbox tries to establish a connection through LNC using the given -// connect phrase and the test mailbox server. -func connectMailbox(ctx context.Context, +// connectMailboxWithPairingPhrase tries to establish a connection through LNC +// using the given connect phrase and the test mailbox server. +func connectMailboxWithPairingPhrase(ctx context.Context, connectPhrase []string) (*grpc.ClientConn, error) { var mnemonicWords [mailbox.NumPassphraseWords]string diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index 24c973cf8..fa25ca80d 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -12,4 +12,8 @@ var allTestCases = []*testCase{ name: "test mode remote", test: testModeRemote, }, + { + name: "test firewall rules", + test: testFirewallRules, + }, } From 5a453cd36cf10b2649e37878255ab4f4eadceaaf Mon Sep 17 00:00:00 2001 From: bitromortac Date: Mon, 31 Oct 2022 14:33:23 +0100 Subject: [PATCH 39/47] firewall: add amount fuzzing Adds helper functions to randomize amounts, timestamps, and booleans. Amounts are randomized based on a percentage and timestamps based on an absolute scale. --- firewall/privacy_mapper.go | 133 +++++++++++++++++++++++++++- firewall/privacy_mapper_test.go | 152 ++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 2 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index 2f63c37c0..cda9b62fe 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -2,8 +2,11 @@ package firewall import ( "context" + "crypto/rand" "errors" "fmt" + "math/big" + "time" "github.com/lightninglabs/lightning-terminal/firewalldb" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" @@ -12,8 +15,22 @@ import ( "google.golang.org/protobuf/proto" ) -// privacyMapperName is the name of the RequestLogger interceptor. -const privacyMapperName = "lit-privacy-mapper" +const ( + // privacyMapperName is the name of the RequestLogger interceptor. + privacyMapperName = "lit-privacy-mapper" + + // amountVariation and timeVariation are used to set the randomization + // of amounts and timestamps that are sent to the autopilot. Changing + // these values may lead to unintended consequences in the behavior of + // the autpilot. + amountVariation = 0.05 + timeVariation = time.Duration(10) * time.Minute + + // minTimeVariation and maxTimeVariation are the acceptable bounds + // between which timeVariation can be set. + minTimeVariation = time.Minute + maxTimeVariation = time.Duration(24) * time.Hour +) var ( // ErrNotSupportedByPrivacyMapper indicates that the invoked RPC method @@ -492,3 +509,115 @@ func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB) func( return r, nil } } + +// hideAmount symmetrically randomizes an amount around a given relative +// variation interval. relativeVariation should be between 0 and 1. +func hideAmount(randIntn func(n int) (int, error), relativeVariation float64, + amount uint64) (uint64, error) { + + if relativeVariation < 0 || relativeVariation > 1 { + return 0, fmt.Errorf("hide amount: relative variation is not "+ + "between allowed bounds of [0, 1], is %v", + relativeVariation) + } + + if amount == 0 { + return 0, nil + } + + // fuzzInterval is smaller than the amount provided fuzzVariation is + // between 0 and 1. + fuzzInterval := uint64(float64(amount) * relativeVariation) + + amountMin := int(amount - fuzzInterval/2) + amountMax := int(amount + fuzzInterval/2) + + randAmount, err := randBetween(randIntn, amountMin, amountMax) + if err != nil { + return 0, err + } + + return uint64(randAmount), nil +} + +// hideTimestamp symmetrically randomizes a unix timestamp given an absolute +// variation interval. The random input is expected to be rand.Intn. +func hideTimestamp(randIntn func(n int) (int, error), + absoluteVariation time.Duration, + timestamp time.Time) (time.Time, error) { + + if absoluteVariation < minTimeVariation || + absoluteVariation > maxTimeVariation { + + return time.Time{}, fmt.Errorf("hide timestamp: absolute time "+ + "variation is out of bounds, have %v", + absoluteVariation) + } + + // Don't fuzz meaningless timestamps. + if timestamp.Add(-absoluteVariation).Unix() < 0 || + timestamp.IsZero() { + + return timestamp, nil + } + + // We vary symmetrically around the provided timestamp. + timeMin := timestamp.Add(-absoluteVariation / 2) + timeMax := timestamp.Add(absoluteVariation / 2) + + timeNs, err := randBetween( + randIntn, int(timeMin.UnixNano()), int(timeMax.UnixNano()), + ) + if err != nil { + return time.Time{}, err + } + + return time.Unix(0, int64(timeNs)), nil +} + +// randBetween generates a random number between [min, max) given a source of +// randomness. +func randBetween(randIntn func(int) (int, error), min, max int) (int, error) { + if max < min { + return 0, fmt.Errorf("min is not allowed to be greater than "+ + "max, (min: %v, max: %v)", min, max) + } + + // We don't want to pass zero to randIntn to avoid panics. + if max == min { + return min, nil + } + + add, err := randIntn(max - min) + if err != nil { + return 0, err + } + + return min + add, nil +} + +// hideBool generates a random bool given a random input. +func hideBool(randIntn func(n int) (int, error)) (bool, error) { + random, err := randIntn(2) + if err != nil { + return false, err + } + + // For testing we may expect larger random numbers, which we map to + // true. + return random >= 1, nil +} + +// CryptoRandIntn generates a random number between [0, n). +func CryptoRandIntn(n int) (int, error) { + if n == 0 { + return 0, nil + } + + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(n))) + if err != nil { + return 0, err + } + + return int(nBig.Int64()), nil +} diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go index c6643c772..8fd8b524c 100644 --- a/firewall/privacy_mapper_test.go +++ b/firewall/privacy_mapper_test.go @@ -3,6 +3,7 @@ package firewall import ( "context" "testing" + "time" "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/session" @@ -366,3 +367,154 @@ func (m *mockPrivacyMapDB) RealToPseudo(real string) (string, error) { } var _ firewalldb.PrivacyMapDB = (*mockPrivacyMapDB)(nil) + +// TestRandBetween tests random number generation for numbers in an interval. +func TestRandBetween(t *testing.T) { + min := 0 + max := 10 + + for i := 0; i < 100; i++ { + val, err := randBetween(CryptoRandIntn, min, max) + require.NoError(t, err) + require.Less(t, val, max) + require.GreaterOrEqual(t, val, min) + } +} + +// TestHideAmount tests that we hide amounts correctly. +func TestHideAmount(t *testing.T) { + testAmount := uint64(10_000) + relativeVariation := 0.05 + fuzzInterval := int(float64(testAmount) * relativeVariation) + + tests := []struct { + name string + amount uint64 + randIntFn func(int) (int, error) + expected uint64 + }{ + { + name: "zero test amount", + randIntFn: func(int) (int, error) { return 0, nil }, + }, + { + name: "test small amount", + randIntFn: func(int) (int, error) { return 0, nil }, + amount: 1, + expected: 1, + }, + { + name: "min value", + randIntFn: func(int) (int, error) { return 0, nil }, + amount: testAmount, + expected: 9750, + }, + { + name: "max value", + randIntFn: func(int) (int, error) { + return fuzzInterval, nil + }, + amount: testAmount, + expected: 10250, + }, + { + name: "some fuzz", + randIntFn: func(int) (int, error) { return 123, nil }, + amount: testAmount, + expected: 9750 + 123, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + val, err := hideAmount( + test.randIntFn, + relativeVariation, + test.amount, + ) + require.NoError(t, err) + require.Equal(t, test.expected, val) + }) + } + + // Subtest with real randomness. + t.Run("real randomness for small numbers", func(t *testing.T) { + for i := 0; i < 1000; i++ { + _, err := hideAmount( + CryptoRandIntn, + relativeVariation, + uint64(i), + ) + require.NoError(t, err) + } + }) +} + +// TestHideTimestamp test correct timestamp hiding. +func TestHideTimestamp(t *testing.T) { + timestamp := time.Unix(1_000_000, 0) + absoluteVariation := time.Duration(10) * time.Minute + + tests := []struct { + name string + randIntFn func(int) (int, error) + timestamp time.Time + expected time.Time + }{ + { + name: "zero timestamp", + randIntFn: func(int) (int, error) { return 0, nil }, + }, + { + name: "min value", + randIntFn: func(int) (int, error) { return 0, nil }, + timestamp: timestamp, + expected: time.Unix(999_700, 0), + }, + { + name: "max value", + randIntFn: func(int) (int, error) { + return int(absoluteVariation), nil + }, + timestamp: timestamp, + expected: time.Unix(1_000_300, 0), + }, + { + name: "some fuzz", + randIntFn: func(int) (int, error) { return 123, nil }, + timestamp: timestamp, + expected: time.Unix(999_700, 123), + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + val, err := hideTimestamp( + test.randIntFn, + absoluteVariation, + test.timestamp, + ) + require.NoError(t, err) + require.Equal(t, test.expected, val) + }) + } +} + +// TestHideBool test correct boolean hiding. +func TestHideBool(t *testing.T) { + val, err := hideBool(func(int) (int, error) { return 100, nil }) + require.NoError(t, err) + require.True(t, val) + + val, err = hideBool(func(int) (int, error) { return 1, nil }) + require.NoError(t, err) + require.True(t, val) + + val, err = hideBool(func(int) (int, error) { return 0, nil }) + require.NoError(t, err) + require.False(t, val) +} From a74ae880c47d6c330e25fd77ab7606bd010e7936 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 4 Nov 2022 10:57:47 +0100 Subject: [PATCH 40/47] firewall: randomize responses with PrivacyMapper Adds amount, timestamp, and channel initiator obfuscation to the two response handlers `handleFwdHistoryResponse` and `handleListChannelsResponse`. In order to preserve privacy and still ensure functioning of algorithms that rely on the randomized data, a trade-off between randomization and accuracy needs to be found. We choose ten minutes for forwarding timestamps as this breaks time correlation of payments. The amount obfuscation is chosen to be 5% and applies to the forwarding amount and channel details to hide balances. We also remove details of pending HTLCs in channels. Random obfuscation for amounts is chosen here instead of rounding to have non-deterministic alteration of amounts, which is especially important for forwardings to also break amount correlation. Randomly varying around a certain value will statistically skew averages less than rounding for algorithms that rely on aggregation of individual data. The privacy mapper is chosen to accept a randomness input in order to ensure deterministic testing even when other handlers are changed in the future. --- firewall/privacy_mapper.go | 165 +++++++++++++++++++++--- firewall/privacy_mapper_test.go | 216 +++++++++++++++++++++++++++++--- terminal.go | 5 + 3 files changed, 348 insertions(+), 38 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index cda9b62fe..d69992cf8 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -46,14 +46,16 @@ var _ mid.RequestInterceptor = (*PrivacyMapper)(nil) // PrivacyMapper is a RequestInterceptor that maps any pseudo names in certain // requests to their real values and vice versa for responses. type PrivacyMapper struct { - newDB firewalldb.NewPrivacyMapDB + newDB firewalldb.NewPrivacyMapDB + randIntn func(int) (int, error) } -// NewPrivacyMapper returns a new instance of PrivacyMapper. -func NewPrivacyMapper(newDB firewalldb.NewPrivacyMapDB) *PrivacyMapper { - return &PrivacyMapper{ - newDB: newDB, - } +// NewPrivacyMapper returns a new instance of PrivacyMapper. The randIntn +// function is used to draw randomness for request field obfuscation. +func NewPrivacyMapper(newDB firewalldb.NewPrivacyMapDB, + randIntn func(int) (int, error)) *PrivacyMapper { + + return &PrivacyMapper{newDB: newDB, randIntn: randIntn} } // Name returns the name of the interceptor. @@ -224,7 +226,7 @@ func (p *PrivacyMapper) checkers( "/lnrpc.Lightning/ForwardingHistory": mid.NewResponseRewriter( &lnrpc.ForwardingHistoryRequest{}, &lnrpc.ForwardingHistoryResponse{}, - handleFwdHistoryResponse(db), + handleFwdHistoryResponse(db, p.randIntn), mid.PassThroughErrorHandler, ), "/lnrpc.Lightning/FeeReport": mid.NewResponseRewriter( @@ -236,7 +238,8 @@ func (p *PrivacyMapper) checkers( &lnrpc.ListChannelsRequest{}, &lnrpc.ListChannelsResponse{}, handleListChannelsRequest(db), - handleListChannelsResponse(db), + handleListChannelsResponse(db, p.randIntn), + mid.PassThroughErrorHandler, ), "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewFullRewriter( @@ -282,15 +285,16 @@ func handleGetInfoRequest(db firewalldb.PrivacyMapDB) func(ctx context.Context, } } -func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB) func( - ctx context.Context, r *lnrpc.ForwardingHistoryResponse) (proto.Message, - error) { +func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB, + randIntn func(int) (int, error)) func(ctx context.Context, + r *lnrpc.ForwardingHistoryResponse) (proto.Message, error) { - return func(ctx context.Context, r *lnrpc.ForwardingHistoryResponse) ( + return func(_ context.Context, r *lnrpc.ForwardingHistoryResponse) ( proto.Message, error) { err := db.Update(func(tx firewalldb.PrivacyMapTx) error { for _, fe := range r.ForwardingEvents { + // Deterministically hide channel ids. chanIn, err := firewalldb.HideUint64( tx, fe.ChanIdIn, ) @@ -306,6 +310,44 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB) func( return err } fe.ChanIdOut = chanOut + + // We randomize the outgoing amount for privacy. + hiddenAmtOutMsat, err := hideAmount( + randIntn, amountVariation, + fe.AmtOutMsat, + ) + if err != nil { + return err + } + fe.AmtOutMsat = hiddenAmtOutMsat + + // We randomize fees for privacy. + hiddenFeeMsat, err := hideAmount( + randIntn, amountVariation, fe.FeeMsat, + ) + if err != nil { + return err + } + fe.FeeMsat = hiddenFeeMsat + + // Populate other fields in a consistent manner. + fe.AmtInMsat = fe.AmtOutMsat + fe.FeeMsat + fe.AmtOut = fe.AmtOutMsat / 1000 + fe.AmtIn = fe.AmtInMsat / 1000 + fe.Fee = fe.FeeMsat / 1000 + + // We randomize the forwarding timestamp. + timestamp := time.Unix(0, int64(fe.TimestampNs)) + hiddenTimestamp, err := hideTimestamp( + randIntn, timeVariation, timestamp, + ) + if err != nil { + return err + } + fe.TimestampNs = uint64( + hiddenTimestamp.UnixNano(), + ) + fe.Timestamp = uint64(hiddenTimestamp.Unix()) } return nil }) @@ -382,22 +424,37 @@ func handleListChannelsRequest(db firewalldb.PrivacyMapDB) func( } } -func handleListChannelsResponse(db firewalldb.PrivacyMapDB) func( - ctx context.Context, r *lnrpc.ListChannelsResponse) (proto.Message, - error) { +func handleListChannelsResponse(db firewalldb.PrivacyMapDB, + randIntn func(int) (int, error)) func(ctx context.Context, + r *lnrpc.ListChannelsResponse) (proto.Message, error) { - return func(ctx context.Context, r *lnrpc.ListChannelsResponse) ( + return func(_ context.Context, r *lnrpc.ListChannelsResponse) ( proto.Message, error) { + hideAmount := func(a int64) (int64, error) { + hiddenAmount, err := hideAmount( + randIntn, amountVariation, uint64(a), + ) + if err != nil { + return 0, err + } + + return int64(hiddenAmount), nil + } + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { for i, c := range r.Channels { + ch := r.Channels[i] + + // Deterministically hide the peer pubkey, + // the channel point, and the channel id. pk, err := firewalldb.HideString( tx, c.RemotePubkey, ) if err != nil { return err } - r.Channels[i].RemotePubkey = pk + ch.RemotePubkey = pk cp, err := firewalldb.HideChanPointStr( tx, c.ChannelPoint, @@ -405,13 +462,83 @@ func handleListChannelsResponse(db firewalldb.PrivacyMapDB) func( if err != nil { return err } - r.Channels[i].ChannelPoint = cp + ch.ChannelPoint = cp cid, err := firewalldb.HideUint64(tx, c.ChanId) if err != nil { return err } - r.Channels[i].ChanId = cid + ch.ChanId = cid + + // We hide the initiator. + initiator, err := hideBool(randIntn) + if err != nil { + return err + } + ch.Initiator = initiator + + // Consider the capacity to be public + // information. We don't care about reserves, as + // having some funds as a balance is the normal + // state over the lifetime of a channel. The + // balance would be zero only for the initial + // state as a non-funder. + + // We randomize local/remote balances. + localBalance, err := hideAmount(c.LocalBalance) + if err != nil { + return err + } + + // We may have a too large value for the local + // balance, restrict it to the capacity. + if localBalance > c.Capacity { + localBalance = c.Capacity + } + if ch.Initiator { + localBalance -= ch.CommitFee + } + ch.LocalBalance = localBalance + + // We adapt the remote balance accordingly. + remoteBalance := c.Capacity - localBalance - + c.CommitFee + if !ch.Initiator { + remoteBalance -= ch.CommitFee + } + ch.RemoteBalance = remoteBalance + + // We hide the total sats sent and received. + hiddenSatsReceived, err := hideAmount( + c.TotalSatoshisReceived, + ) + if err != nil { + return err + } + ch.TotalSatoshisReceived = hiddenSatsReceived + + hiddenSatsSent, err := hideAmount( + c.TotalSatoshisSent, + ) + if err != nil { + return err + } + ch.TotalSatoshisSent = hiddenSatsSent + + // We only keep track of the number of unsettled + // HTLCs. + ch.PendingHtlcs = make( + []*lnrpc.HTLC, len(ch.PendingHtlcs), + ) + + // We hide the unsettled balance. + unsettled, err := hideAmount( + c.UnsettledBalance, + ) + if err != nil { + return err + } + ch.UnsettledBalance = unsettled } return nil diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go index 8fd8b524c..8b5fb2908 100644 --- a/firewall/privacy_mapper_test.go +++ b/firewall/privacy_mapper_test.go @@ -48,28 +48,56 @@ func TestPrivacyMapper(t *testing.T) { msg: &lnrpc.ForwardingHistoryResponse{ ForwardingEvents: []*lnrpc.ForwardingEvent{ { - AmtIn: 100, - ChanIdIn: 123, - ChanIdOut: 321, + AmtIn: 2_000, + AmtInMsat: 2_000_000, + AmtOut: 1_000, + AmtOutMsat: 1_000_000, + Fee: 1_000, + FeeMsat: 1_000_000, + Timestamp: 1_000, + TimestampNs: 1_000_000_000_000, + ChanIdIn: 123, + ChanIdOut: 321, }, { - Fee: 200, - ChanIdIn: 678, - ChanIdOut: 876, + AmtIn: 3_000, + AmtInMsat: 3_000_000, + AmtOut: 2_000, + AmtOutMsat: 2_000_000, + Fee: 1_000, + FeeMsat: 1_000_000, + Timestamp: 1_000, + TimestampNs: 1_000_000_000_000, + ChanIdIn: 678, + ChanIdOut: 876, }, }, }, expectedReplacement: &lnrpc.ForwardingHistoryResponse{ ForwardingEvents: []*lnrpc.ForwardingEvent{ { - AmtIn: 100, - ChanIdIn: 5178778334600911958, - ChanIdOut: 3446430762436373227, + AmtIn: 1_950, + AmtInMsat: 1_950_200, + AmtOut: 975, + AmtOutMsat: 975_100, + Fee: 975, + FeeMsat: 975_100, + Timestamp: 700, + TimestampNs: 700_000_000_100, + ChanIdIn: 5178778334600911958, + ChanIdOut: 3446430762436373227, }, { - Fee: 200, - ChanIdIn: 8672172843977902018, - ChanIdOut: 1378354177616075123, + AmtIn: 2_925, + AmtInMsat: 2_925_200, + AmtOut: 1_950, + AmtOutMsat: 1_950_100, + Fee: 975, + FeeMsat: 975_100, + Timestamp: 700, + TimestampNs: 700_000_000_100, + ChanIdIn: 8672172843977902018, + ChanIdOut: 1378354177616075123, }, }, }, @@ -121,18 +149,34 @@ func TestPrivacyMapper(t *testing.T) { msg: &lnrpc.ListChannelsResponse{ Channels: []*lnrpc.Channel{ { - RemotePubkey: "01020304", - ChanId: 123, - ChannelPoint: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:0", + Capacity: 1_000_000, + RemoteBalance: 600_000, + LocalBalance: 499_000, + CommitFee: 1_000, + TotalSatoshisSent: 500_000, + TotalSatoshisReceived: 450_000, + RemotePubkey: "01020304", + Initiator: false, + ChanId: 123, + ChannelPoint: "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd:0", + PendingHtlcs: []*lnrpc.HTLC{{HashLock: []byte("aaaa")}, {HashLock: []byte("bbbb")}}, }, }, }, expectedReplacement: &lnrpc.ListChannelsResponse{ Channels: []*lnrpc.Channel{ { - RemotePubkey: "c8134495", - ChanId: 5178778334600911958, - ChannelPoint: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384:2161781494", + Capacity: 1_000_000, + RemoteBalance: 513_375, + LocalBalance: 485_625, + CommitFee: 1_000, + TotalSatoshisSent: 487_600, + TotalSatoshisReceived: 438_850, + RemotePubkey: "c8134495", + Initiator: true, + ChanId: 5178778334600911958, + ChannelPoint: "097ef666a61919ff3413b3b701eae3a5cbac08f70c0ca567806e1fa6acbfe384:2161781494", + PendingHtlcs: []*lnrpc.HTLC{{}, {}}, }, }, }, @@ -247,7 +291,10 @@ func TestPrivacyMapper(t *testing.T) { } db := newMockDB(t, mapPreloadRealToPseudo, sessionID) - p := NewPrivacyMapper(db.NewSessionDB) + + // randIntn is used for deterministic testing. + randIntn := func(n int) (int, error) { return 100, nil } + p := NewPrivacyMapper(db.NewSessionDB, randIntn) for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -286,6 +333,111 @@ func TestPrivacyMapper(t *testing.T) { ) }) } + + // Subtest to test behavior with real randomness. + t.Run("Response with randomness", func(t *testing.T) { + msg := &lnrpc.ForwardingHistoryResponse{ + ForwardingEvents: []*lnrpc.ForwardingEvent{ + { + AmtIn: 2_000, + AmtInMsat: 2_000_000, + AmtOut: 1_000, + AmtOutMsat: 1_000_000, + Fee: 0, + FeeMsat: 1, + Timestamp: 1_000_000, + TimestampNs: 1_000_000 * 1e9, + ChanIdIn: 123, + ChanIdOut: 321, + }, + }, + } + rawMsg, err := proto.Marshal(msg) + require.NoError(t, err) + + p = NewPrivacyMapper(db.NewSessionDB, CryptoRandIntn) + require.NoError(t, err) + + // We test the independent outgoing amount (incoming amount + // would also be dependend on the fee variation). + amtOutMsat := msg.ForwardingEvents[0].AmtOutMsat + amtInterval := uint64(amountVariation * float64(amtOutMsat)) + minAmt := amtOutMsat - amtInterval/2 + maxAmt := amtOutMsat + amtInterval/2 + + // We keep track of the timestamp. We test only the timestamp in + // seconds as there can be numerical inaccuracies with the + // nanosecond one. + timestamp := msg.ForwardingEvents[0].Timestamp + timestampInterval := uint64(timeVariation) / 1e9 + minTime := timestamp - timestampInterval/2 + maxTime := timestamp + timestampInterval/2 + + // We need a certain number of samples to have statistical + // accuracy. + numSamples := 10_000 + + // We require a five percent accuracy for 10_000 samples. + relativeTestAccuracy := 0.05 + + amounts := make([]uint64, numSamples) + timestamps := make([]uint64, numSamples) + + for i := 0; i < numSamples; i++ { + interceptReq := &rpcperms.InterceptionRequest{ + Type: rpcperms.TypeResponse, + Macaroon: mac, + RawMacaroon: macBytes, + FullURI: "/lnrpc.Lightning/ForwardingHistory", + ProtoSerialized: rawMsg, + ProtoTypeName: string( + proto.MessageName(msg), + ), + } + + mwReq, err := interceptReq.ToRPC(1, 2) + require.NoError(t, err) + + resp, err := p.Intercept(context.Background(), mwReq) + require.NoError(t, err) + + feedback := resp.GetFeedback() + + fw := &lnrpc.ForwardingHistoryResponse{} + err = proto.Unmarshal( + feedback.ReplacementSerialized, fw, + ) + require.NoError(t, err) + + amounts[i] = fw.ForwardingEvents[0].AmtOutMsat + require.LessOrEqual(t, amounts[i], maxAmt) + require.GreaterOrEqual(t, amounts[i], minAmt) + + timestamps[i] = fw.ForwardingEvents[0].Timestamp + require.LessOrEqual(t, timestamps[i], maxTime) + require.GreaterOrEqual(t, timestamps[i], minTime) + } + + // The formula for the expected variance is taken from + // https://en.wikipedia.org/wiki/Continuous_uniform_distribution + expectedVar := func(min, max uint64) uint64 { + return (max - min) * (max - min) / 12 + } + + // Test amounts for mean and variance. + expectedAmtVariance := expectedVar(minAmt, maxAmt) + require.InEpsilon(t, expectedAmtVariance, variance(amounts), + relativeTestAccuracy) + require.InEpsilon(t, amtOutMsat, mean(amounts), + relativeTestAccuracy) + + // Test timestamps for mean and variance. + expectedTimeVariance := expectedVar(minTime, maxTime) + require.InEpsilon(t, expectedTimeVariance, variance(timestamps), + relativeTestAccuracy) + require.InEpsilon(t, timestamp, mean(timestamps), + relativeTestAccuracy) + }) } type mockDB map[string]*mockPrivacyMapDB @@ -518,3 +670,29 @@ func TestHideBool(t *testing.T) { require.NoError(t, err) require.False(t, val) } + +// mean computes the mean of the given slice of numbers. +func mean(numbers []uint64) uint64 { + sum := uint64(0) + + for _, n := range numbers { + sum += n + } + + return sum / uint64(len(numbers)) +} + +// variance computes the variance of the given slice of numbers. +func variance(numbers []uint64) uint64 { + mean := mean(numbers) + sum := 0.0 + + // We divide in each step to have smaller numbers. + norm := float64(len(numbers) - 1) + + for _, n := range numbers { + sum += float64((n-mean)*(n-mean)) / norm + } + + return uint64(sum) +} diff --git a/terminal.go b/terminal.go index d9ca88f6d..0385a8197 100644 --- a/terminal.go +++ b/terminal.go @@ -728,7 +728,12 @@ func (g *LightningTerminal) startSubservers() error { g.accountServiceStarted = true requestLogger := firewall.NewRequestLogger(g.firewallDB) + privacyMapper := firewall.NewPrivacyMapper( + g.firewallDB.PrivacyDB, firewall.CryptoRandIntn, + ) + mw := []mid.RequestInterceptor{ + privacyMapper, g.accountService, requestLogger, } From c3d93383650ee4d11805d4161c95bd636376d128 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 1 Dec 2022 14:25:14 +0200 Subject: [PATCH 41/47] multi: use URIs in session macaroon instead of perms Also includes an itest to assert that sessions work with very large headers. --- itest/litd_firewall_test.go | 120 ++++++++++++++++++++++++++++++++ itest/litd_test_list_on_test.go | 4 ++ session_rpcserver.go | 26 +++---- 3 files changed, 132 insertions(+), 18 deletions(-) diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index 780f70141..0d07e2456 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -19,6 +19,7 @@ import ( "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -1137,6 +1138,125 @@ func testPeerAndChannelRestrictRules(net *NetworkHarness, t *harnessTest) { require.NoError(t.t, err) } +func testLargeHttpHeader(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // First we add all LND's permissions so that any call we make to LND to + // test that the connection is working will succeed. + perms := lnd.MainRPCServerPermissions() + + // Now we pad the above valid perms with a bunch of junk perms. This is + // done in order to make the macaroon that will be sent in the header + // very big. + for i := 0; i < 800; i++ { + uniqueString := fmt.Sprintf("unique long string %d", i) + perms[uniqueString] = []bakery.Op{ + { + Entity: uniqueString, + Action: "fake-action", + }, + } + } + + // We first override the autopilots features set. We create a feature + // with a very large permissions set. This is done so that we can test + // that a grpc/http header of a certain size does not break things. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "TestFeature": { + Rules: map[string]*mock.RuleRanges{}, + Permissions: perms, + }, + }) + + // We expect a non-empty alias to be returned. + aliceInfo, err := net.Alice.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + require.NoError(t.t, err) + require.NotEmpty(t.t, aliceInfo.Alias) + + // We create a connection to the Alice node's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.TLSCertPath) + require.NoError(t.t, err) + + macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Add a new Autopilot session that subscribes to a "Test", feature. + // This call is expected to also result in Litd registering this session + // with the mock autopilot server. + sessResp, err := litClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: map[string]*litrpc.FeatureConfig{ + "TestFeature": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{}, + }, + }, + }, + // Switch the privacy mapper off for simplicity’s sake. + NoPrivacyMapper: true, + }, + ) + require.NoError(t.t, err) + + // From the response, we can extract Lit's local public key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + lndConn := lnrpc.NewLightningClient(pilotConn) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the HealthCheck feature. + metaInfo := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "TestFeature", + } + caveat, err := metaInfo.ToCaveat() + require.NoError(t.t, err) + caveatCreds := metaDataInjector.addCaveat(caveat) + + // Now we assert that the size of the macaroon that will go in the + // request header is larger than a certain threshold. + meta, err := caveatCreds.Creds.GetRequestMetadata(ctx, "") + require.NoError(t.t, err) + require.Greater(t.t, len([]byte(meta[HeaderMacaroon])), 40000) + + // Assert that requests from the autopilot work with this large header. + getInfoReq, err := lndConn.GetInfo( + ctx, &lnrpc.GetInfoRequest{}, caveatCreds, + ) + require.NoError(t.t, err) + require.Equal(t.t, aliceInfo.Alias, getInfoReq.Alias) +} + // connectMailboxWithRemoteKey tries to establish a connection through LNC using // the given local and remote keys and the test mailbox server. func connectMailboxWithRemoteKey(ctx context.Context, diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index fa25ca80d..282f5b576 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -16,4 +16,8 @@ var allTestCases = []*testCase{ name: "test firewall rules", test: testFirewallRules, }, + { + name: "test large http header", + test: testLargeHttpHeader, + }, } diff --git a/session_rpcserver.go b/session_rpcserver.go index 569d8f678..740cbddf9 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -934,33 +934,23 @@ func (s *sessionRpcServer) AddAutopilotSession(ctx context.Context, // Gather all the permissions we need to add to the macaroon given the // feature list. - var dedupedPerms = make(map[string]map[string]bool) + var dedupedPerms = make(map[string]bool) for name, feature := range autopilotFeatureMap { if _, ok := req.Features[name]; !ok { continue } - for _, ops := range feature.Permissions { - for _, op := range ops { - if dedupedPerms[op.Entity] == nil { - dedupedPerms[op.Entity] = make( - map[string]bool, - ) - } - - dedupedPerms[op.Entity][op.Action] = true - } + for uri := range feature.Permissions { + dedupedPerms[uri] = true } } var perms []bakery.Op - for entity, actions := range dedupedPerms { - for action := range actions { - perms = append(perms, bakery.Op{ - Entity: entity, - Action: action, - }) - } + for uri := range dedupedPerms { + perms = append(perms, bakery.Op{ + Entity: macaroons.PermissionEntityCustomURI, + Action: uri, + }) } rulesCaveatStr, err := firewall.RulesToCaveat(interceptRules) From 1506f5732077afd3affb79198abbe9a83c0e77dd Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 11 Jan 2023 12:44:35 +0200 Subject: [PATCH 42/47] privacy_mapper: construct new response msgs In this commit, in the response handlers of the privacy mapper, we construct new response messages instead of overwriting existing messages in order to minimize the chances of private data slipping through accidentally. --- firewall/privacy_mapper.go | 232 ++++++++++++++++++++++++++----------- 1 file changed, 162 insertions(+), 70 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index d69992cf8..11d67fd69 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -221,7 +221,7 @@ func (p *PrivacyMapper) checkers( return map[string]mid.RoundTripChecker{ "/lnrpc.Lightning/GetInfo": mid.NewResponseRewriter( &lnrpc.GetInfoRequest{}, &lnrpc.GetInfoResponse{}, - handleGetInfoRequest(db), mid.PassThroughErrorHandler, + handleGetInfoResponse(db), mid.PassThroughErrorHandler, ), "/lnrpc.Lightning/ForwardingHistory": mid.NewResponseRewriter( &lnrpc.ForwardingHistoryRequest{}, @@ -239,7 +239,6 @@ func (p *PrivacyMapper) checkers( &lnrpc.ListChannelsResponse{}, handleListChannelsRequest(db), handleListChannelsResponse(db, p.randIntn), - mid.PassThroughErrorHandler, ), "/lnrpc.Lightning/UpdateChannelPolicy": mid.NewFullRewriter( @@ -252,23 +251,23 @@ func (p *PrivacyMapper) checkers( } } -func handleGetInfoRequest(db firewalldb.PrivacyMapDB) func(ctx context.Context, +func handleGetInfoResponse(db firewalldb.PrivacyMapDB) func(ctx context.Context, r *lnrpc.GetInfoResponse) (proto.Message, error) { return func(ctx context.Context, r *lnrpc.GetInfoResponse) ( proto.Message, error) { + var pseudoPubKey string err := db.Update( func(tx firewalldb.PrivacyMapTx) error { var err error - pk, err := firewalldb.HideString( + pseudoPubKey, err = firewalldb.HideString( tx, r.IdentityPubkey, ) if err != nil { return err } - r.IdentityPubkey = pk return nil }, ) @@ -276,12 +275,29 @@ func handleGetInfoRequest(db firewalldb.PrivacyMapDB) func(ctx context.Context, return nil, err } - // Hide our Alias and URI from the autopilot - // server. - r.Alias = "" - r.Uris = nil - - return r, nil + return &lnrpc.GetInfoResponse{ + // We purposefully hide our alias and URIs from the + // autopilot server. + Alias: "", + Color: "", + Uris: nil, + Version: r.Version, + CommitHash: r.CommitHash, + IdentityPubkey: pseudoPubKey, + NumPendingChannels: r.NumPendingChannels, + NumActiveChannels: r.NumActiveChannels, + NumInactiveChannels: r.NumInactiveChannels, + NumPeers: r.NumPeers, + BlockHeight: r.BlockHeight, + BlockHash: r.BlockHash, + BestHeaderTimestamp: r.BestHeaderTimestamp, + SyncedToChain: r.SyncedToChain, + SyncedToGraph: r.SyncedToGraph, + Testnet: r.Testnet, + Chains: r.Chains, + Features: r.Features, + RequireHtlcInterceptor: r.RequireHtlcInterceptor, + }, nil } } @@ -292,8 +308,12 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB, return func(_ context.Context, r *lnrpc.ForwardingHistoryResponse) ( proto.Message, error) { + fwdEvents := make( + []*lnrpc.ForwardingEvent, len(r.ForwardingEvents), + ) + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { - for _, fe := range r.ForwardingEvents { + for i, fe := range r.ForwardingEvents { // Deterministically hide channel ids. chanIn, err := firewalldb.HideUint64( tx, fe.ChanIdIn, @@ -301,7 +321,6 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB, if err != nil { return err } - fe.ChanIdIn = chanIn chanOut, err := firewalldb.HideUint64( tx, fe.ChanIdOut, @@ -309,45 +328,55 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB, if err != nil { return err } - fe.ChanIdOut = chanOut // We randomize the outgoing amount for privacy. - hiddenAmtOutMsat, err := hideAmount( + amtOutMsat, err := hideAmount( randIntn, amountVariation, fe.AmtOutMsat, ) if err != nil { return err } - fe.AmtOutMsat = hiddenAmtOutMsat // We randomize fees for privacy. - hiddenFeeMsat, err := hideAmount( + feeMsat, err := hideAmount( randIntn, amountVariation, fe.FeeMsat, ) if err != nil { return err } - fe.FeeMsat = hiddenFeeMsat // Populate other fields in a consistent manner. - fe.AmtInMsat = fe.AmtOutMsat + fe.FeeMsat - fe.AmtOut = fe.AmtOutMsat / 1000 - fe.AmtIn = fe.AmtInMsat / 1000 - fe.Fee = fe.FeeMsat / 1000 + amtInMsat := amtOutMsat + feeMsat + amtOut := amtOutMsat / 1000 + amtIn := amtInMsat / 1000 + fee := feeMsat / 1000 // We randomize the forwarding timestamp. - timestamp := time.Unix(0, int64(fe.TimestampNs)) - hiddenTimestamp, err := hideTimestamp( - randIntn, timeVariation, timestamp, + timestamp, err := hideTimestamp( + randIntn, timeVariation, + time.Unix(0, int64(fe.TimestampNs)), ) if err != nil { return err } - fe.TimestampNs = uint64( - hiddenTimestamp.UnixNano(), - ) - fe.Timestamp = uint64(hiddenTimestamp.Unix()) + + fwdEvents[i] = &lnrpc.ForwardingEvent{ + ChanIdIn: chanIn, + ChanIdOut: chanOut, + AmtIn: amtIn, + AmtOut: amtOut, + Fee: fee, + FeeMsat: feeMsat, + AmtInMsat: amtInMsat, + AmtOutMsat: amtOutMsat, + TimestampNs: uint64( + timestamp.UnixNano(), + ), + Timestamp: uint64( + timestamp.Unix(), + ), + } } return nil }) @@ -355,7 +384,10 @@ func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB, return nil, err } - return r, nil + return &lnrpc.ForwardingHistoryResponse{ + ForwardingEvents: fwdEvents, + LastOffsetIndex: r.LastOffsetIndex, + }, nil } } @@ -366,8 +398,10 @@ func handleFeeReportResponse(db firewalldb.PrivacyMapDB) func( return func(ctx context.Context, r *lnrpc.FeeReportResponse) ( proto.Message, error) { + chanFees := make([]*lnrpc.ChannelFeeReport, len(r.ChannelFees)) + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { - for _, c := range r.ChannelFees { + for i, c := range r.ChannelFees { chanID, err := firewalldb.HideUint64( tx, c.ChanId, ) @@ -382,8 +416,13 @@ func handleFeeReportResponse(db firewalldb.PrivacyMapDB) func( return err } - c.ChannelPoint = chanPoint - c.ChanId = chanID + chanFees[i] = &lnrpc.ChannelFeeReport{ + ChanId: chanID, + ChannelPoint: chanPoint, + BaseFeeMsat: c.BaseFeeMsat, + FeePerMil: c.FeePerMil, + FeeRate: c.FeeRate, + } } return nil @@ -392,7 +431,12 @@ func handleFeeReportResponse(db firewalldb.PrivacyMapDB) func( return nil, err } - return r, nil + return &lnrpc.FeeReportResponse{ + ChannelFees: chanFees, + DayFeeSum: r.DayFeeSum, + WeekFeeSum: r.WeekFeeSum, + MonthFeeSum: r.MonthFeeSum, + }, nil } } @@ -442,40 +486,38 @@ func handleListChannelsResponse(db firewalldb.PrivacyMapDB, return int64(hiddenAmount), nil } + channels := make([]*lnrpc.Channel, len(r.Channels)) + err := db.Update(func(tx firewalldb.PrivacyMapTx) error { for i, c := range r.Channels { - ch := r.Channels[i] - // Deterministically hide the peer pubkey, // the channel point, and the channel id. - pk, err := firewalldb.HideString( + remotePub, err := firewalldb.HideString( tx, c.RemotePubkey, ) if err != nil { return err } - ch.RemotePubkey = pk - cp, err := firewalldb.HideChanPointStr( + chanPoint, err := firewalldb.HideChanPointStr( tx, c.ChannelPoint, ) if err != nil { return err } - ch.ChannelPoint = cp - cid, err := firewalldb.HideUint64(tx, c.ChanId) + chanID, err := firewalldb.HideUint64( + tx, c.ChanId, + ) if err != nil { return err } - ch.ChanId = cid // We hide the initiator. initiator, err := hideBool(randIntn) if err != nil { return err } - ch.Initiator = initiator // Consider the capacity to be public // information. We don't care about reserves, as @@ -495,50 +537,85 @@ func handleListChannelsResponse(db firewalldb.PrivacyMapDB, if localBalance > c.Capacity { localBalance = c.Capacity } - if ch.Initiator { - localBalance -= ch.CommitFee + if initiator { + localBalance -= c.CommitFee } - ch.LocalBalance = localBalance // We adapt the remote balance accordingly. remoteBalance := c.Capacity - localBalance - c.CommitFee - if !ch.Initiator { - remoteBalance -= ch.CommitFee + if !initiator { + remoteBalance -= c.CommitFee } - ch.RemoteBalance = remoteBalance // We hide the total sats sent and received. - hiddenSatsReceived, err := hideAmount( + satsReceived, err := hideAmount( c.TotalSatoshisReceived, ) if err != nil { return err } - ch.TotalSatoshisReceived = hiddenSatsReceived - hiddenSatsSent, err := hideAmount( + satsSent, err := hideAmount( c.TotalSatoshisSent, ) if err != nil { return err } - ch.TotalSatoshisSent = hiddenSatsSent - // We only keep track of the number of unsettled - // HTLCs. - ch.PendingHtlcs = make( - []*lnrpc.HTLC, len(ch.PendingHtlcs), + // We only keep track of the _number_ of + // unsettled HTLCs. + pendingHtlcs := make( + []*lnrpc.HTLC, len(c.PendingHtlcs), ) // We hide the unsettled balance. - unsettled, err := hideAmount( - c.UnsettledBalance, - ) + unsettled, err := hideAmount(c.UnsettledBalance) if err != nil { return err } - ch.UnsettledBalance = unsettled + + //nolint:lll + channels[i] = &lnrpc.Channel{ + // Items we adjust. + RemotePubkey: remotePub, + ChannelPoint: chanPoint, + ChanId: chanID, + Initiator: initiator, + LocalBalance: localBalance, + RemoteBalance: remoteBalance, + TotalSatoshisReceived: satsReceived, + TotalSatoshisSent: satsSent, + UnsettledBalance: unsettled, + PendingHtlcs: pendingHtlcs, + + // Items that we zero out. + CloseAddress: "", + PushAmountSat: 0, + AliasScids: nil, + ZeroConfConfirmedScid: 0, + + // Items we keep as is. + Active: c.Active, + Capacity: c.Capacity, + CommitFee: c.CommitFee, + CommitWeight: c.CommitWeight, + FeePerKw: c.FeePerKw, + NumUpdates: c.NumUpdates, + CsvDelay: c.CsvDelay, + Private: c.Private, + ChanStatusFlags: c.ChanStatusFlags, + LocalChanReserveSat: c.LocalChanReserveSat, + RemoteChanReserveSat: c.RemoteChanReserveSat, + StaticRemoteKey: c.StaticRemoteKey, + CommitmentType: c.CommitmentType, + Lifetime: c.Lifetime, + Uptime: c.Uptime, + ThawHeight: c.ThawHeight, + LocalConstraints: c.LocalConstraints, + RemoteConstraints: c.RemoteConstraints, + ZeroConf: c.ZeroConf, + } } return nil @@ -547,7 +624,9 @@ func handleListChannelsResponse(db firewalldb.PrivacyMapDB, return nil, err } - return r, nil + return &lnrpc.ListChannelsResponse{ + Channels: channels, + }, nil } } @@ -608,12 +687,21 @@ func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB) func( return func(ctx context.Context, r *lnrpc.PolicyUpdateResponse) ( proto.Message, error) { - if len(r.FailedUpdates) == 0 { - return nil, nil - } + failedUpdates := make( + []*lnrpc.FailedUpdate, len(r.FailedUpdates), + ) err := db.Update(func(tx firewalldb.PrivacyMapTx) error { - for _, u := range r.FailedUpdates { + for i, u := range r.FailedUpdates { + failedUpdates[i] = &lnrpc.FailedUpdate{ + Reason: u.Reason, + UpdateError: u.UpdateError, + } + + if u.Outpoint == nil { + continue + } + txid, index, err := firewalldb.HideChanPoint( tx, u.Outpoint.TxidStr, u.Outpoint.OutputIndex, @@ -622,9 +710,11 @@ func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB) func( return err } - u.Outpoint.TxidBytes = nil - u.Outpoint.TxidStr = txid - u.Outpoint.OutputIndex = index + failedUpdates[i].Outpoint = &lnrpc.OutPoint{ + TxidBytes: nil, + TxidStr: txid, + OutputIndex: index, + } } return nil @@ -633,7 +723,9 @@ func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB) func( return nil, err } - return r, nil + return &lnrpc.PolicyUpdateResponse{ + FailedUpdates: failedUpdates, + }, nil } } From 6a5a8c68f6305a02d12899b3a233d98930131ec4 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 3 Jan 2023 18:17:32 +0200 Subject: [PATCH 43/47] go.mod+go.sum: bump tlv version Bump the tlv version to the version that supports uncapped tlv streams. --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index fab5f875f..d56fa6359 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/lightningnetwork/lnd v0.15.5-beta github.com/lightningnetwork/lnd/cert v1.1.1 github.com/lightningnetwork/lnd/kvdb v1.3.1 - github.com/lightningnetwork/lnd/tlv v1.0.3 + github.com/lightningnetwork/lnd/tlv v1.1.0 github.com/lightningnetwork/lnd/tor v1.0.1 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76 diff --git a/go.sum b/go.sum index e05a6bdd1..297ff5d8f 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,7 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20220413172512-bf64c8bdbbbf/go.mod h1:0Q github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.2/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ= github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= @@ -601,8 +602,9 @@ github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXj github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4= github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk= github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= -github.com/lightningnetwork/lnd/tlv v1.0.3 h1:0xBZcPuXagP6f7TY/RnLNR4igE21ov6qUdTr5NyvhhI= github.com/lightningnetwork/lnd/tlv v1.0.3/go.mod h1:dzR/aZetBri+ZY/fHbwV06fNn/3UID6htQzbHfREFdo= +github.com/lightningnetwork/lnd/tlv v1.1.0 h1:gsyte75HVuA/X59O+BhaISHM6OobZ0YesPbdu+xG1h0= +github.com/lightningnetwork/lnd/tlv v1.1.0/go.mod h1:0+JKp4un47MG1lnj6jKa8woNeB1X7w3yF4MZB1NHiiE= github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= github.com/lightningnetwork/lnd/tor v1.0.1 h1:A11FrpU0Y//g+fA827W4VnjOeoIvExONdchlLX8wYkA= github.com/lightningnetwork/lnd/tor v1.0.1/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64= From d3cab70928cd9ee01dca789d63f7622ac0824682 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 13 Jan 2023 15:22:04 +0200 Subject: [PATCH 44/47] cmd/litcli: create account sessions Create LNC account sessions through litcli --- cmd/litcli/sessions.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/litcli/sessions.go b/cmd/litcli/sessions.go index dff4e484c..3a18fb4e0 100644 --- a/cmd/litcli/sessions.go +++ b/cmd/litcli/sessions.go @@ -69,7 +69,7 @@ var addSessionCommand = cli.Command{ Usage: "session type to be created which will " + "determine the permissions a user has when " + "connecting with the session. Options " + - "include readonly|admin|custom", + "include readonly|admin|account|custom", Value: "readonly", }, cli.StringSliceFlag{ @@ -85,6 +85,13 @@ var addSessionCommand = cli.Command{ "For example, '/lnrpc\\..*' will result in " + "all `lnrpc` permissions being included.", }, + cli.StringFlag{ + Name: "account_id", + Usage: "The account id that should be used for " + + "the account session. Note that this flag " + + "will only be used if the 'type' flag is " + + "set to 'account'.", + }, }, } @@ -122,6 +129,7 @@ func addSession(ctx *cli.Context) error { MailboxServerAddr: ctx.String("mailboxserveraddr"), DevServer: ctx.Bool("devserver"), MacaroonCustomPermissions: macPerms, + AccountId: ctx.String("account_id"), }, ) if err != nil { @@ -139,6 +147,8 @@ func parseSessionType(sessionType string) (litrpc.SessionType, error) { return litrpc.SessionType_TYPE_MACAROON_ADMIN, nil case "readonly": return litrpc.SessionType_TYPE_MACAROON_READONLY, nil + case "account": + return litrpc.SessionType_TYPE_MACAROON_ACCOUNT, nil case "custom": return litrpc.SessionType_TYPE_MACAROON_CUSTOM, nil default: From 2d57c25850a8c39e9a44945b907ccc134e0ce53f Mon Sep 17 00:00:00 2001 From: bitromortac Date: Fri, 20 Jan 2023 08:42:08 +0200 Subject: [PATCH 45/47] firewall: redefine obfuscation interval --- firewall/privacy_mapper.go | 17 +++----- firewall/privacy_mapper_test.go | 70 +++++++++++++++++---------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/firewall/privacy_mapper.go b/firewall/privacy_mapper.go index 11d67fd69..8bf55b174 100644 --- a/firewall/privacy_mapper.go +++ b/firewall/privacy_mapper.go @@ -537,16 +537,9 @@ func handleListChannelsResponse(db firewalldb.PrivacyMapDB, if localBalance > c.Capacity { localBalance = c.Capacity } - if initiator { - localBalance -= c.CommitFee - } // We adapt the remote balance accordingly. - remoteBalance := c.Capacity - localBalance - - c.CommitFee - if !initiator { - remoteBalance -= c.CommitFee - } + remoteBalance := c.Capacity - localBalance // We hide the total sats sent and received. satsReceived, err := hideAmount( @@ -748,8 +741,8 @@ func hideAmount(randIntn func(n int) (int, error), relativeVariation float64, // between 0 and 1. fuzzInterval := uint64(float64(amount) * relativeVariation) - amountMin := int(amount - fuzzInterval/2) - amountMax := int(amount + fuzzInterval/2) + amountMin := int(amount - fuzzInterval) + amountMax := int(amount + fuzzInterval) randAmount, err := randBetween(randIntn, amountMin, amountMax) if err != nil { @@ -781,8 +774,8 @@ func hideTimestamp(randIntn func(n int) (int, error), } // We vary symmetrically around the provided timestamp. - timeMin := timestamp.Add(-absoluteVariation / 2) - timeMax := timestamp.Add(absoluteVariation / 2) + timeMin := timestamp.Add(-absoluteVariation) + timeMax := timestamp.Add(absoluteVariation) timeNs, err := randBetween( randIntn, int(timeMin.UnixNano()), int(timeMax.UnixNano()), diff --git a/firewall/privacy_mapper_test.go b/firewall/privacy_mapper_test.go index 8b5fb2908..3778363b6 100644 --- a/firewall/privacy_mapper_test.go +++ b/firewall/privacy_mapper_test.go @@ -76,26 +76,26 @@ func TestPrivacyMapper(t *testing.T) { expectedReplacement: &lnrpc.ForwardingHistoryResponse{ ForwardingEvents: []*lnrpc.ForwardingEvent{ { - AmtIn: 1_950, - AmtInMsat: 1_950_200, - AmtOut: 975, - AmtOutMsat: 975_100, - Fee: 975, - FeeMsat: 975_100, - Timestamp: 700, - TimestampNs: 700_000_000_100, + AmtIn: 1_900, + AmtInMsat: 1_900_200, + AmtOut: 950, + AmtOutMsat: 950_100, + Fee: 950, + FeeMsat: 950_100, + Timestamp: 400, + TimestampNs: 400_000_000_100, ChanIdIn: 5178778334600911958, ChanIdOut: 3446430762436373227, }, { - AmtIn: 2_925, - AmtInMsat: 2_925_200, - AmtOut: 1_950, - AmtOutMsat: 1_950_100, - Fee: 975, - FeeMsat: 975_100, - Timestamp: 700, - TimestampNs: 700_000_000_100, + AmtIn: 2_850, + AmtInMsat: 2_850_200, + AmtOut: 1_900, + AmtOutMsat: 1_900_100, + Fee: 950, + FeeMsat: 950_100, + Timestamp: 400, + TimestampNs: 400_000_000_100, ChanIdIn: 8672172843977902018, ChanIdOut: 1378354177616075123, }, @@ -167,11 +167,11 @@ func TestPrivacyMapper(t *testing.T) { Channels: []*lnrpc.Channel{ { Capacity: 1_000_000, - RemoteBalance: 513_375, - LocalBalance: 485_625, + RemoteBalance: 525_850, + LocalBalance: 474_150, CommitFee: 1_000, - TotalSatoshisSent: 487_600, - TotalSatoshisReceived: 438_850, + TotalSatoshisSent: 475_100, + TotalSatoshisReceived: 427_600, RemotePubkey: "c8134495", Initiator: true, ChanId: 5178778334600911958, @@ -362,16 +362,16 @@ func TestPrivacyMapper(t *testing.T) { // would also be dependend on the fee variation). amtOutMsat := msg.ForwardingEvents[0].AmtOutMsat amtInterval := uint64(amountVariation * float64(amtOutMsat)) - minAmt := amtOutMsat - amtInterval/2 - maxAmt := amtOutMsat + amtInterval/2 + minAmt := amtOutMsat - amtInterval + maxAmt := amtOutMsat + amtInterval // We keep track of the timestamp. We test only the timestamp in // seconds as there can be numerical inaccuracies with the // nanosecond one. timestamp := msg.ForwardingEvents[0].Timestamp timestampInterval := uint64(timeVariation) / 1e9 - minTime := timestamp - timestampInterval/2 - maxTime := timestamp + timestampInterval/2 + minTime := timestamp - timestampInterval + maxTime := timestamp + timestampInterval // We need a certain number of samples to have statistical // accuracy. @@ -537,7 +537,9 @@ func TestRandBetween(t *testing.T) { func TestHideAmount(t *testing.T) { testAmount := uint64(10_000) relativeVariation := 0.05 - fuzzInterval := int(float64(testAmount) * relativeVariation) + absoluteVariation := int(float64(testAmount) * relativeVariation) + lowerBound := testAmount - uint64(absoluteVariation) + upperBound := testAmount + uint64(absoluteVariation) tests := []struct { name string @@ -559,21 +561,21 @@ func TestHideAmount(t *testing.T) { name: "min value", randIntFn: func(int) (int, error) { return 0, nil }, amount: testAmount, - expected: 9750, + expected: lowerBound, }, { name: "max value", randIntFn: func(int) (int, error) { - return fuzzInterval, nil + return int(upperBound - lowerBound), nil }, amount: testAmount, - expected: 10250, + expected: upperBound, }, { name: "some fuzz", randIntFn: func(int) (int, error) { return 123, nil }, amount: testAmount, - expected: 9750 + 123, + expected: lowerBound + 123, }, } @@ -608,6 +610,8 @@ func TestHideAmount(t *testing.T) { func TestHideTimestamp(t *testing.T) { timestamp := time.Unix(1_000_000, 0) absoluteVariation := time.Duration(10) * time.Minute + lowerBound := timestamp.Add(-absoluteVariation) + upperBound := timestamp.Add(absoluteVariation) tests := []struct { name string @@ -623,21 +627,21 @@ func TestHideTimestamp(t *testing.T) { name: "min value", randIntFn: func(int) (int, error) { return 0, nil }, timestamp: timestamp, - expected: time.Unix(999_700, 0), + expected: lowerBound, }, { name: "max value", randIntFn: func(int) (int, error) { - return int(absoluteVariation), nil + return int(upperBound.Sub(lowerBound)), nil }, timestamp: timestamp, - expected: time.Unix(1_000_300, 0), + expected: upperBound, }, { name: "some fuzz", randIntFn: func(int) (int, error) { return 123, nil }, timestamp: timestamp, - expected: time.Unix(999_700, 123), + expected: lowerBound.Add(time.Duration(123)), }, } From 8e61eb90da035514dea79b3135919c864e22a29f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 26 Jan 2023 17:13:04 +0200 Subject: [PATCH 46/47] multi: make request logger level configurable --- config.go | 4 ++ firewall/config.go | 20 ++++++ firewall/request_logger.go | 127 ++++++++++++++++++++++++++++--------- terminal.go | 8 ++- 4 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 firewall/config.go diff --git a/config.go b/config.go index aa482c00d..cb9d741a7 100644 --- a/config.go +++ b/config.go @@ -18,6 +18,7 @@ import ( "github.com/lightninglabs/faraday/chain" "github.com/lightninglabs/faraday/frdrpcserver" "github.com/lightninglabs/lightning-terminal/autopilotserver" + "github.com/lightninglabs/lightning-terminal/firewall" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopd" @@ -188,6 +189,8 @@ type Config struct { Autopilot *autopilotserver.Config `group:"Autopilot server options" namespace:"autopilot"` + Firewall *firewall.Config `group:"Firewall options" namespace:"firewall"` + // faradayRpcConfig is a subset of faraday's full configuration that is // passed into faraday's RPC server. faradayRpcConfig *frdrpcserver.Config @@ -331,6 +334,7 @@ func defaultConfig() *Config { Autopilot: &autopilotserver.Config{ PingCadence: time.Hour, }, + Firewall: firewall.DefaultConfig(), } } diff --git a/firewall/config.go b/firewall/config.go new file mode 100644 index 000000000..c0fdb758e --- /dev/null +++ b/firewall/config.go @@ -0,0 +1,20 @@ +package firewall + +// Config holds all config options for the firewall. +type Config struct { + RequestLogger *RequestLoggerConfig `group:"request-logger" namespace:"request-logger" description:"request logger settings"` +} + +// RequestLoggerConfig holds all the config options for the request logger. +type RequestLoggerConfig struct { + RequestLoggerLevel RequestLoggerLevel `long:"level" description:"Set the request logger level. Options include 'all', 'full' and 'interceptor''"` +} + +// DefaultConfig constructs the default firewall Config struct. +func DefaultConfig() *Config { + return &Config{ + RequestLogger: &RequestLoggerConfig{ + RequestLoggerLevel: RequestLoggerLevelInterceptor, + }, + } +} diff --git a/firewall/request_logger.go b/firewall/request_logger.go index 358f4802e..c3b6e61f8 100644 --- a/firewall/request_logger.go +++ b/firewall/request_logger.go @@ -3,6 +3,7 @@ package firewall import ( "context" "fmt" + "strings" "sync" "time" @@ -12,6 +13,7 @@ import ( "github.com/lightninglabs/lightning-terminal/session" "github.com/lightninglabs/protobuf-hex-display/jsonpb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" ) const ( @@ -35,10 +37,20 @@ var ( _ mid.RequestInterceptor = (*RequestLogger)(nil) ) +type RequestLoggerLevel string + +const ( + RequestLoggerLevelInterceptor = "interceptor" + RequestLoggerLevelAll = "all" + RequestLoggerLevelFull = "full" +) + // RequestLogger is a RequestInterceptor that just logs incoming RPC requests. type RequestLogger struct { actionsDB firewalldb.ActionsWriteDB + shouldLogAction func(ri *RequestInfo) (bool, bool) + // reqIDToAction is a map from request ID to an ActionLocator that can // be used to find the corresponding action. This is used so that // requests and responses can be easily linked. The mu mutex must be @@ -48,11 +60,55 @@ type RequestLogger struct { } // NewRequestLogger creates a new RequestLogger. -func NewRequestLogger(actionsDB firewalldb.ActionsWriteDB) *RequestLogger { - return &RequestLogger{ - actionsDB: actionsDB, - reqIDToAction: make(map[uint64]*firewalldb.ActionLocator), +func NewRequestLogger(cfg *RequestLoggerConfig, + actionsDB firewalldb.ActionsWriteDB) (*RequestLogger, error) { + + hasInterceptorCaveat := func(caveats []string) bool { + for _, c := range caveats { + if strings.HasPrefix(c, macaroons.CondLndCustom) { + return true + } + } + + return false + } + + var shouldLogAction func(ri *RequestInfo) (bool, bool) + switch cfg.RequestLoggerLevel { + // Only log requests that have an interceptor caveat attached. + case RequestLoggerLevelInterceptor: + shouldLogAction = func(ri *RequestInfo) (bool, bool) { + if hasInterceptorCaveat(ri.Caveats) { + return true, true + } + + return false, false + } + + // Log all requests but only log request params if the request + // has an interceptor caveat. + case RequestLoggerLevelAll: + shouldLogAction = func(ri *RequestInfo) (bool, bool) { + return true, hasInterceptorCaveat(ri.Caveats) + } + + // Log all requests will all request parameters. + case RequestLoggerLevelFull: + shouldLogAction = func(ri *RequestInfo) (bool, bool) { + return true, true + } + + default: + return nil, fmt.Errorf("unknown request logger level: %s. "+ + "Expected either 'interceptor', 'all' or 'full'", + cfg.RequestLoggerLevel) } + + return &RequestLogger{ + shouldLogAction: shouldLogAction, + actionsDB: actionsDB, + reqIDToAction: make(map[uint64]*firewalldb.ActionLocator), + }, nil } // Name returns the name of the interceptor. @@ -89,6 +145,11 @@ func (r *RequestLogger) Intercept(_ context.Context, return mid.RPCOk(req) } + shouldLogAction, withPayloadData := r.shouldLogAction(ri) + if !shouldLogAction { + return mid.RPCOk(req) + } + log.Tracef("RequestLogger: Intercepting %v", ri) switch ri.MWRequestType { @@ -97,7 +158,7 @@ func (r *RequestLogger) Intercept(_ context.Context, // Parse incoming requests and act on them. case MWRequestTypeRequest: - return mid.RPCErr(req, r.addNewAction(ri)) + return mid.RPCErr(req, r.addNewAction(ri, withPayloadData)) // Parse and possibly manipulate outgoing responses. case MWRequestTypeResponse: @@ -120,7 +181,9 @@ func (r *RequestLogger) Intercept(_ context.Context, } // addNewAction persists the new action to the db. -func (r *RequestLogger) addNewAction(ri *RequestInfo) error { +func (r *RequestLogger) addNewAction(ri *RequestInfo, + withPayloadData bool) error { + // If no macaroon is provided, then an empty 4-byte array is used as the // session ID. Otherwise, the macaroon is used to derive a session ID. var sessionID [4]byte @@ -132,34 +195,40 @@ func (r *RequestLogger) addNewAction(ri *RequestInfo) error { } } - msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized) - if err != nil { - return err + action := &firewalldb.Action{ + RPCMethod: ri.URI, + AttemptedAt: time.Now(), + State: firewalldb.ActionStateInit, } - jsonMarshaler := &jsonpb.Marshaler{ - EmitDefaults: true, - OrigName: true, - } + if withPayloadData { + msg, err := mid.ParseProtobuf(ri.GRPCMessageType, ri.Serialized) + if err != nil { + return err + } - jsonStr, err := jsonMarshaler.MarshalToString(proto.MessageV1(msg)) - if err != nil { - return fmt.Errorf("unable to decode response: %v", err) - } + jsonMarshaler := &jsonpb.Marshaler{ + EmitDefaults: true, + OrigName: true, + } - action := &firewalldb.Action{ - RPCMethod: ri.URI, - RPCParamsJson: []byte(jsonStr), - AttemptedAt: time.Now(), - State: firewalldb.ActionStateInit, - } + jsonStr, err := jsonMarshaler.MarshalToString( + proto.MessageV1(msg), + ) + if err != nil { + return fmt.Errorf("unable to decode response: %v", err) + } - if ri.MetaInfo != nil { - action.ActorName = ri.MetaInfo.ActorName - action.FeatureName = ri.MetaInfo.Feature - action.Trigger = ri.MetaInfo.Trigger - action.Intent = ri.MetaInfo.Intent - action.StructuredJsonData = ri.MetaInfo.StructuredJsonData + action.RPCParamsJson = []byte(jsonStr) + + meta := ri.MetaInfo + if meta != nil { + action.ActorName = meta.ActorName + action.FeatureName = meta.Feature + action.Trigger = meta.Trigger + action.Intent = meta.Intent + action.StructuredJsonData = meta.StructuredJsonData + } } id, err := r.actionsDB.AddAction(sessionID, action) diff --git a/terminal.go b/terminal.go index 0385a8197..550f1612e 100644 --- a/terminal.go +++ b/terminal.go @@ -727,7 +727,13 @@ func (g *LightningTerminal) startSubservers() error { } g.accountServiceStarted = true - requestLogger := firewall.NewRequestLogger(g.firewallDB) + requestLogger, err := firewall.NewRequestLogger( + g.cfg.Firewall.RequestLogger, g.firewallDB, + ) + if err != nil { + return fmt.Errorf("error creating new request logger") + } + privacyMapper := firewall.NewPrivacyMapper( g.firewallDB.PrivacyDB, firewall.CryptoRandIntn, ) From 378a91fe686f24ad3e369edd2d4d174a06140a95 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 13 Feb 2023 20:11:28 +0200 Subject: [PATCH 47/47] app+proto: run make protos --- app/src/types/generated/lit-sessions_pb.d.ts | 4 + app/src/types/generated/lit-sessions_pb.js | 29 +++- app/src/util/tests/sampleData.ts | 2 + proto/lit-sessions.proto | 138 ++++++++++++++++++- 4 files changed, 171 insertions(+), 2 deletions(-) diff --git a/app/src/types/generated/lit-sessions_pb.d.ts b/app/src/types/generated/lit-sessions_pb.d.ts index 5b2b7bf81..c9ce8cc50 100644 --- a/app/src/types/generated/lit-sessions_pb.d.ts +++ b/app/src/types/generated/lit-sessions_pb.d.ts @@ -150,6 +150,9 @@ export class Session extends jspb.Message { getAutopilotFeatureInfoMap(): jspb.Map; clearAutopilotFeatureInfoMap(): void; + getRevokedAt(): string; + setRevokedAt(value: string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Session.AsObject; static toObject(includeInstance: boolean, msg: Session): Session.AsObject; @@ -177,6 +180,7 @@ export namespace Session { macaroonRecipe?: MacaroonRecipe.AsObject, accountId: string, autopilotFeatureInfoMap: Array<[string, RulesMap.AsObject]>, + revokedAt: string, } } diff --git a/app/src/types/generated/lit-sessions_pb.js b/app/src/types/generated/lit-sessions_pb.js index 81a67cac4..e09f51593 100644 --- a/app/src/types/generated/lit-sessions_pb.js +++ b/app/src/types/generated/lit-sessions_pb.js @@ -757,7 +757,8 @@ proto.litrpc.Session.toObject = function(includeInstance, msg) { createdAt: jspb.Message.getFieldWithDefault(msg, 11, "0"), macaroonRecipe: (f = msg.getMacaroonRecipe()) && proto.litrpc.MacaroonRecipe.toObject(includeInstance, f), accountId: jspb.Message.getFieldWithDefault(msg, 13, ""), - autopilotFeatureInfoMap: (f = msg.getAutopilotFeatureInfoMap()) ? f.toObject(includeInstance, proto.litrpc.RulesMap.toObject) : [] + autopilotFeatureInfoMap: (f = msg.getAutopilotFeatureInfoMap()) ? f.toObject(includeInstance, proto.litrpc.RulesMap.toObject) : [], + revokedAt: jspb.Message.getFieldWithDefault(msg, 16, "0") }; if (includeInstance) { @@ -857,6 +858,10 @@ proto.litrpc.Session.deserializeBinaryFromReader = function(msg, reader) { jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readMessage, proto.litrpc.RulesMap.deserializeBinaryFromReader, ""); }); break; + case 16: + var value = /** @type {string} */ (reader.readUint64String()); + msg.setRevokedAt(value); + break; default: reader.skipField(); break; @@ -989,6 +994,13 @@ proto.litrpc.Session.serializeBinaryToWriter = function(message, writer) { if (f && f.getLength() > 0) { f.serializeBinary(15, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeMessage, proto.litrpc.RulesMap.serializeBinaryToWriter); } + f = message.getRevokedAt(); + if (parseInt(f, 10) !== 0) { + writer.writeUint64String( + 16, + f + ); + } }; @@ -1333,6 +1345,21 @@ proto.litrpc.Session.prototype.clearAutopilotFeatureInfoMap = function() { }; +/** + * optional uint64 revoked_at = 16; + * @return {string} + */ +proto.litrpc.Session.prototype.getRevokedAt = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 16, "0")); +}; + + +/** @param {string} value */ +proto.litrpc.Session.prototype.setRevokedAt = function(value) { + jspb.Message.setProto3StringIntField(this, 16, value); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/app/src/util/tests/sampleData.ts b/app/src/util/tests/sampleData.ts index b86761bd5..8a352d712 100644 --- a/app/src/util/tests/sampleData.ts +++ b/app/src/util/tests/sampleData.ts @@ -900,6 +900,7 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', accountId: '', + revokedAt: '453300000000', autopilotFeatureInfoMap: [ [ 'SampleFeature', @@ -978,6 +979,7 @@ export const litListSessions: LIT.ListSessionsResponse.AsObject = { sessionState: LIT.SessionState.STATE_EXPIRED, sessionType: LIT.SessionType.TYPE_UI_PASSWORD, createdAt: '253300000000', + revokedAt: '453300000000', accountId: '', autopilotFeatureInfoMap: [ [ diff --git a/proto/lit-sessions.proto b/proto/lit-sessions.proto index 9661c0cf2..27737f332 100644 --- a/proto/lit-sessions.proto +++ b/proto/lit-sessions.proto @@ -19,7 +19,7 @@ enum SessionType { TYPE_MACAROON_ADMIN = 1; TYPE_MACAROON_CUSTOM = 2; TYPE_UI_PASSWORD = 3; - reserved 4; + TYPE_AUTOPILOT = 4; TYPE_MACAROON_ACCOUNT = 5; } @@ -67,6 +67,8 @@ message AddSessionResponse { } message Session { + bytes id = 14; + string label = 1; SessionState session_state = 2; @@ -92,6 +94,18 @@ message Session { MacaroonRecipe macaroon_recipe = 12; string account_id = 13; + + map autopilot_feature_info = 15; + + /* + The unix timestamp indicating the time at which the session was revoked. + Note that this field has not been around since the beginning and so it + could be the case that a session has been revoked but that this field + will not have been set for that session. Therefore, it is suggested that + readers should not assume that if this field is zero that the session is + not revoked. Readers should instead first check the session_state field. + */ + uint64 revoked_at = 16 [jstype = JS_STRING]; } message MacaroonRecipe { @@ -113,3 +127,125 @@ message RevokeSessionRequest { message RevokeSessionResponse { } + +message RulesMap { + /* + A map of rule name to RuleValue. The RuleValue should be parsed based on + the name of the rule. + */ + map rules = 1; +} + +message RuleValue { + oneof value { + RateLimit rate_limit = 1; + ChannelPolicyBounds chan_policy_bounds = 2; + HistoryLimit history_limit = 3; + OffChainBudget off_chain_budget = 4; + OnChainBudget on_chain_budget = 5; + SendToSelf send_to_self = 6; + ChannelRestrict channel_restrict = 7; + PeerRestrict peer_restrict = 8; + } +} + +message RateLimit { + /* + The rate limit for read-only calls. + */ + Rate read_limit = 1; + + /* + The rate limit for write/execution calls. + */ + Rate write_limit = 2; +} + +message Rate { + /* + The number of times a call is allowed in num_hours number of hours. + */ + uint32 iterations = 1; + + /* + The number of hours in which the iterations count takes place over. + */ + uint32 num_hours = 2; +} + +message HistoryLimit { + /* + The absolute unix timestamp in seconds before which no information should + be shared. This should only be set if duration is not set. + */ + uint64 start_time = 1 [jstype = JS_STRING]; + + /* + The maximum relative duration in seconds that a request is allowed to query + for. This should only be set if start_time is not set. + */ + uint64 duration = 2 [jstype = JS_STRING]; +} + +message ChannelPolicyBounds { + /* + The minimum base fee in msat that the autopilot can set for a channel. + */ + uint64 min_base_msat = 1 [jstype = JS_STRING]; + + /* + The maximum base fee in msat that the autopilot can set for a channel. + */ + uint64 max_base_msat = 2 [jstype = JS_STRING]; + + /* + The minimum ppm fee in msat that the autopilot can set for a channel. + */ + uint32 min_rate_ppm = 3; + + /* + The maximum ppm fee in msat that the autopilot can set for a channel. + */ + uint32 max_rate_ppm = 4; + + /* + The minimum cltv delta that the autopilot may set for a channel. + */ + uint32 min_cltv_delta = 5; + + /* + The maximum cltv delta that the autopilot may set for a channel. + */ + uint32 max_cltv_delta = 6; + + /* + The minimum htlc msat that the autopilot may set for a channel. + */ + uint64 min_htlc_msat = 7 [jstype = JS_STRING]; + + /* + The maximum htlc msat that the autopilot may set for a channel. + */ + uint64 max_htlc_msat = 8 [jstype = JS_STRING]; +} + +message OffChainBudget { + uint64 max_amt_msat = 1 [jstype = JS_STRING]; + uint64 max_fees_msat = 2 [jstype = JS_STRING]; +} + +message OnChainBudget { + uint64 absolute_amt_sats = 1 [jstype = JS_STRING]; + uint64 max_sat_per_v_byte = 2 [jstype = JS_STRING]; +} + +message SendToSelf { +} + +message ChannelRestrict { + repeated uint64 channel_ids = 1 [jstype = JS_STRING]; +} + +message PeerRestrict { + repeated string peer_ids = 1; +} \ No newline at end of file