diff --git a/cmd/lnmuxd/lnmux_proto/lnmux.pb.go b/cmd/lnmuxd/lnmux_proto/lnmux.pb.go index 9b1d330..c647898 100644 --- a/cmd/lnmuxd/lnmux_proto/lnmux.pb.go +++ b/cmd/lnmuxd/lnmux_proto/lnmux.pb.go @@ -23,28 +23,22 @@ const ( type SubscribeSingleInvoiceResponse_InvoiceState int32 const ( - SubscribeSingleInvoiceResponse_STATE_OPEN SubscribeSingleInvoiceResponse_InvoiceState = 0 - SubscribeSingleInvoiceResponse_STATE_ACCEPTED SubscribeSingleInvoiceResponse_InvoiceState = 1 - SubscribeSingleInvoiceResponse_STATE_SETTLE_REQUESTED SubscribeSingleInvoiceResponse_InvoiceState = 2 - SubscribeSingleInvoiceResponse_STATE_SETTLED SubscribeSingleInvoiceResponse_InvoiceState = 3 - SubscribeSingleInvoiceResponse_STATE_CANCELLED SubscribeSingleInvoiceResponse_InvoiceState = 4 + SubscribeSingleInvoiceResponse_STATE_ACCEPTED SubscribeSingleInvoiceResponse_InvoiceState = 0 + SubscribeSingleInvoiceResponse_STATE_SETTLE_REQUESTED SubscribeSingleInvoiceResponse_InvoiceState = 1 + SubscribeSingleInvoiceResponse_STATE_SETTLED SubscribeSingleInvoiceResponse_InvoiceState = 2 ) // Enum value maps for SubscribeSingleInvoiceResponse_InvoiceState. var ( SubscribeSingleInvoiceResponse_InvoiceState_name = map[int32]string{ - 0: "STATE_OPEN", - 1: "STATE_ACCEPTED", - 2: "STATE_SETTLE_REQUESTED", - 3: "STATE_SETTLED", - 4: "STATE_CANCELLED", + 0: "STATE_ACCEPTED", + 1: "STATE_SETTLE_REQUESTED", + 2: "STATE_SETTLED", } SubscribeSingleInvoiceResponse_InvoiceState_value = map[string]int32{ - "STATE_OPEN": 0, - "STATE_ACCEPTED": 1, - "STATE_SETTLE_REQUESTED": 2, - "STATE_SETTLED": 3, - "STATE_CANCELLED": 4, + "STATE_ACCEPTED": 0, + "STATE_SETTLE_REQUESTED": 1, + "STATE_SETTLED": 2, } ) @@ -75,58 +69,6 @@ func (SubscribeSingleInvoiceResponse_InvoiceState) EnumDescriptor() ([]byte, []i return file_lnmux_proto_rawDescGZIP(), []int{3, 0} } -type SubscribeSingleInvoiceResponse_CancelledReason int32 - -const ( - SubscribeSingleInvoiceResponse_REASON_NONE SubscribeSingleInvoiceResponse_CancelledReason = 0 - SubscribeSingleInvoiceResponse_REASON_EXPIRED SubscribeSingleInvoiceResponse_CancelledReason = 1 - SubscribeSingleInvoiceResponse_REASON_ACCEPT_TIMEOUT SubscribeSingleInvoiceResponse_CancelledReason = 2 - SubscribeSingleInvoiceResponse_REASON_EXTERNAL SubscribeSingleInvoiceResponse_CancelledReason = 3 -) - -// Enum value maps for SubscribeSingleInvoiceResponse_CancelledReason. -var ( - SubscribeSingleInvoiceResponse_CancelledReason_name = map[int32]string{ - 0: "REASON_NONE", - 1: "REASON_EXPIRED", - 2: "REASON_ACCEPT_TIMEOUT", - 3: "REASON_EXTERNAL", - } - SubscribeSingleInvoiceResponse_CancelledReason_value = map[string]int32{ - "REASON_NONE": 0, - "REASON_EXPIRED": 1, - "REASON_ACCEPT_TIMEOUT": 2, - "REASON_EXTERNAL": 3, - } -) - -func (x SubscribeSingleInvoiceResponse_CancelledReason) Enum() *SubscribeSingleInvoiceResponse_CancelledReason { - p := new(SubscribeSingleInvoiceResponse_CancelledReason) - *p = x - return p -} - -func (x SubscribeSingleInvoiceResponse_CancelledReason) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (SubscribeSingleInvoiceResponse_CancelledReason) Descriptor() protoreflect.EnumDescriptor { - return file_lnmux_proto_enumTypes[1].Descriptor() -} - -func (SubscribeSingleInvoiceResponse_CancelledReason) Type() protoreflect.EnumType { - return &file_lnmux_proto_enumTypes[1] -} - -func (x SubscribeSingleInvoiceResponse_CancelledReason) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use SubscribeSingleInvoiceResponse_CancelledReason.Descriptor instead. -func (SubscribeSingleInvoiceResponse_CancelledReason) EnumDescriptor() ([]byte, []int) { - return file_lnmux_proto_rawDescGZIP(), []int{3, 1} -} - type AddInvoiceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -313,8 +255,7 @@ type SubscribeSingleInvoiceResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - State SubscribeSingleInvoiceResponse_InvoiceState `protobuf:"varint,1,opt,name=state,proto3,enum=lnmux.SubscribeSingleInvoiceResponse_InvoiceState" json:"state,omitempty"` - CancelledReason SubscribeSingleInvoiceResponse_CancelledReason `protobuf:"varint,2,opt,name=cancelled_reason,json=cancelledReason,proto3,enum=lnmux.SubscribeSingleInvoiceResponse_CancelledReason" json:"cancelled_reason,omitempty"` + State SubscribeSingleInvoiceResponse_InvoiceState `protobuf:"varint,1,opt,name=state,proto3,enum=lnmux.SubscribeSingleInvoiceResponse_InvoiceState" json:"state,omitempty"` } func (x *SubscribeSingleInvoiceResponse) Reset() { @@ -353,14 +294,7 @@ func (x *SubscribeSingleInvoiceResponse) GetState() SubscribeSingleInvoiceRespon if x != nil { return x.State } - return SubscribeSingleInvoiceResponse_STATE_OPEN -} - -func (x *SubscribeSingleInvoiceResponse) GetCancelledReason() SubscribeSingleInvoiceResponse_CancelledReason { - if x != nil { - return x.CancelledReason - } - return SubscribeSingleInvoiceResponse_REASON_NONE + return SubscribeSingleInvoiceResponse_STATE_ACCEPTED } type SettleInvoiceRequest struct { @@ -557,66 +491,51 @@ var file_lnmux_proto_rawDesc = []byte{ 0x73, 0x68, 0x22, 0x33, 0x0a, 0x1d, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xac, 0x03, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x73, + 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xbd, 0x01, 0x0a, 0x1e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x60, 0x0a, 0x10, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, - 0x64, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x35, - 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x52, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x0f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, - 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x76, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, - 0x41, 0x54, 0x45, 0x5f, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, - 0x53, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, - 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, - 0x54, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x22, 0x66, - 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, - 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x58, 0x50, - 0x49, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, - 0x5f, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, - 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x58, 0x54, 0x45, - 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x03, 0x22, 0x2a, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x0a, 0x14, 0x43, + 0x74, 0x61, 0x74, 0x65, 0x22, 0x51, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x41, 0x43, + 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x45, + 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x22, 0x2a, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x74, 0x6c, + 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x0a, 0x14, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xcd, 0x02, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, + 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x6c, 0x6e, + 0x6d, 0x75, 0x78, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x41, 0x64, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x67, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, + 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x6c, 0x6e, 0x6d, + 0x75, 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, + 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x25, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x53, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x6d, + 0x75, 0x78, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, + 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0x17, 0x0a, 0x15, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0xcd, 0x02, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x0a, - 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x6d, - 0x75, 0x78, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x41, 0x64, 0x64, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x67, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, - 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, - 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x25, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x4a, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, - 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, - 0x78, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x53, - 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, - 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x70, 0x61, 0x79, 0x2f, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x5f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x2e, 0x43, 0x61, 0x6e, 0x63, + 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x62, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x70, 0x61, 0x79, 0x2f, 0x6c, 0x6e, 0x6d, 0x75, 0x78, 0x5f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -631,36 +550,34 @@ func file_lnmux_proto_rawDescGZIP() []byte { return file_lnmux_proto_rawDescData } -var file_lnmux_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_lnmux_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_lnmux_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_lnmux_proto_goTypes = []interface{}{ - (SubscribeSingleInvoiceResponse_InvoiceState)(0), // 0: lnmux.SubscribeSingleInvoiceResponse.InvoiceState - (SubscribeSingleInvoiceResponse_CancelledReason)(0), // 1: lnmux.SubscribeSingleInvoiceResponse.CancelledReason - (*AddInvoiceRequest)(nil), // 2: lnmux.AddInvoiceRequest - (*AddInvoiceResponse)(nil), // 3: lnmux.AddInvoiceResponse - (*SubscribeSingleInvoiceRequest)(nil), // 4: lnmux.SubscribeSingleInvoiceRequest - (*SubscribeSingleInvoiceResponse)(nil), // 5: lnmux.SubscribeSingleInvoiceResponse - (*SettleInvoiceRequest)(nil), // 6: lnmux.SettleInvoiceRequest - (*SettleInvoiceResponse)(nil), // 7: lnmux.SettleInvoiceResponse - (*CancelInvoiceRequest)(nil), // 8: lnmux.CancelInvoiceRequest - (*CancelInvoiceResponse)(nil), // 9: lnmux.CancelInvoiceResponse + (SubscribeSingleInvoiceResponse_InvoiceState)(0), // 0: lnmux.SubscribeSingleInvoiceResponse.InvoiceState + (*AddInvoiceRequest)(nil), // 1: lnmux.AddInvoiceRequest + (*AddInvoiceResponse)(nil), // 2: lnmux.AddInvoiceResponse + (*SubscribeSingleInvoiceRequest)(nil), // 3: lnmux.SubscribeSingleInvoiceRequest + (*SubscribeSingleInvoiceResponse)(nil), // 4: lnmux.SubscribeSingleInvoiceResponse + (*SettleInvoiceRequest)(nil), // 5: lnmux.SettleInvoiceRequest + (*SettleInvoiceResponse)(nil), // 6: lnmux.SettleInvoiceResponse + (*CancelInvoiceRequest)(nil), // 7: lnmux.CancelInvoiceRequest + (*CancelInvoiceResponse)(nil), // 8: lnmux.CancelInvoiceResponse } var file_lnmux_proto_depIdxs = []int32{ 0, // 0: lnmux.SubscribeSingleInvoiceResponse.state:type_name -> lnmux.SubscribeSingleInvoiceResponse.InvoiceState - 1, // 1: lnmux.SubscribeSingleInvoiceResponse.cancelled_reason:type_name -> lnmux.SubscribeSingleInvoiceResponse.CancelledReason - 2, // 2: lnmux.Service.AddInvoice:input_type -> lnmux.AddInvoiceRequest - 4, // 3: lnmux.Service.SubscribeSingleInvoice:input_type -> lnmux.SubscribeSingleInvoiceRequest - 6, // 4: lnmux.Service.SettleInvoice:input_type -> lnmux.SettleInvoiceRequest - 8, // 5: lnmux.Service.CancelInvoice:input_type -> lnmux.CancelInvoiceRequest - 3, // 6: lnmux.Service.AddInvoice:output_type -> lnmux.AddInvoiceResponse - 5, // 7: lnmux.Service.SubscribeSingleInvoice:output_type -> lnmux.SubscribeSingleInvoiceResponse - 7, // 8: lnmux.Service.SettleInvoice:output_type -> lnmux.SettleInvoiceResponse - 9, // 9: lnmux.Service.CancelInvoice:output_type -> lnmux.CancelInvoiceResponse - 6, // [6:10] is the sub-list for method output_type - 2, // [2:6] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 1, // 1: lnmux.Service.AddInvoice:input_type -> lnmux.AddInvoiceRequest + 3, // 2: lnmux.Service.SubscribeSingleInvoice:input_type -> lnmux.SubscribeSingleInvoiceRequest + 5, // 3: lnmux.Service.SettleInvoice:input_type -> lnmux.SettleInvoiceRequest + 7, // 4: lnmux.Service.CancelInvoice:input_type -> lnmux.CancelInvoiceRequest + 2, // 5: lnmux.Service.AddInvoice:output_type -> lnmux.AddInvoiceResponse + 4, // 6: lnmux.Service.SubscribeSingleInvoice:output_type -> lnmux.SubscribeSingleInvoiceResponse + 6, // 7: lnmux.Service.SettleInvoice:output_type -> lnmux.SettleInvoiceResponse + 8, // 8: lnmux.Service.CancelInvoice:output_type -> lnmux.CancelInvoiceResponse + 5, // [5:9] is the sub-list for method output_type + 1, // [1:5] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_lnmux_proto_init() } @@ -771,7 +688,7 @@ func file_lnmux_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lnmux_proto_rawDesc, - NumEnums: 2, + NumEnums: 1, NumMessages: 8, NumExtensions: 0, NumServices: 1, diff --git a/cmd/lnmuxd/lnmux_proto/lnmux.pb.validate.go b/cmd/lnmuxd/lnmux_proto/lnmux.pb.validate.go index c890064..f7c27a8 100644 --- a/cmd/lnmuxd/lnmux_proto/lnmux.pb.validate.go +++ b/cmd/lnmuxd/lnmux_proto/lnmux.pb.validate.go @@ -261,8 +261,6 @@ func (m *SubscribeSingleInvoiceResponse) Validate() error { // no validation rules for State - // no validation rules for CancelledReason - return nil } diff --git a/cmd/lnmuxd/lnmux_proto/lnmux.proto b/cmd/lnmuxd/lnmux_proto/lnmux.proto index 935bf3f..ceb8712 100644 --- a/cmd/lnmuxd/lnmux_proto/lnmux.proto +++ b/cmd/lnmuxd/lnmux_proto/lnmux.proto @@ -33,23 +33,12 @@ message SubscribeSingleInvoiceRequest { message SubscribeSingleInvoiceResponse { enum InvoiceState { - STATE_OPEN = 0; - STATE_ACCEPTED = 1; - STATE_SETTLE_REQUESTED = 2; - STATE_SETTLED = 3; - STATE_CANCELLED = 4; - } - - enum CancelledReason { - REASON_NONE = 0; - REASON_EXPIRED = 1; - REASON_ACCEPT_TIMEOUT = 2; - REASON_EXTERNAL = 3; + STATE_ACCEPTED = 0; + STATE_SETTLE_REQUESTED = 1; + STATE_SETTLED = 2; } InvoiceState state = 1; - - CancelledReason cancelled_reason = 2; } message SettleInvoiceRequest { diff --git a/cmd/lnmuxd/run.go b/cmd/lnmuxd/run.go index fac7c12..e6e020c 100644 --- a/cmd/lnmuxd/run.go +++ b/cmd/lnmuxd/run.go @@ -81,6 +81,7 @@ func runAction(c *cli.Context) error { HtlcHoldDuration: 30 * time.Second, AcceptTimeout: 60 * time.Second, Logger: log, + PrivKey: identityKey, AutoSettle: cfg.AutoSettle, }, ) diff --git a/cmd/lnmuxd/server.go b/cmd/lnmuxd/server.go index 8e2c2cc..9ff111d 100644 --- a/cmd/lnmuxd/server.go +++ b/cmd/lnmuxd/server.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "math/rand" "time" "github.com/bottlepay/lnmux" @@ -39,8 +38,6 @@ func newServer(creator *lnmux.InvoiceCreator, registry *lnmux.InvoiceRegistry) ( func marshallInvoiceState(state persistence.InvoiceState) lnmux_proto.SubscribeSingleInvoiceResponse_InvoiceState { switch state { - case persistence.InvoiceStateOpen: - return lnmux_proto.SubscribeSingleInvoiceResponse_STATE_OPEN case persistence.InvoiceStateAccepted: return lnmux_proto.SubscribeSingleInvoiceResponse_STATE_ACCEPTED @@ -51,33 +48,11 @@ func marshallInvoiceState(state persistence.InvoiceState) lnmux_proto.SubscribeS case persistence.InvoiceStateSettled: return lnmux_proto.SubscribeSingleInvoiceResponse_STATE_SETTLED - case persistence.InvoiceStateCancelled: - return lnmux_proto.SubscribeSingleInvoiceResponse_STATE_CANCELLED - default: panic("unknown invoice state") } } -func marshallCancelledReason(reason persistence.CancelledReason) lnmux_proto.SubscribeSingleInvoiceResponse_CancelledReason { - switch reason { - case persistence.CancelledReasonNone: - return lnmux_proto.SubscribeSingleInvoiceResponse_REASON_NONE - - case persistence.CancelledReasonExpired: - return lnmux_proto.SubscribeSingleInvoiceResponse_REASON_EXPIRED - - case persistence.CancelledReasonAcceptTimeout: - return lnmux_proto.SubscribeSingleInvoiceResponse_REASON_ACCEPT_TIMEOUT - - case persistence.CancelledReasonExternal: - return lnmux_proto.SubscribeSingleInvoiceResponse_REASON_EXTERNAL - - default: - panic("unknown cancelled reason") - } -} - func (s *server) SubscribeSingleInvoice(req *lnmux_proto.SubscribeSingleInvoiceRequest, subscription lnmux_proto.Service_SubscribeSingleInvoiceServer) error { @@ -136,8 +111,7 @@ func (s *server) SubscribeSingleInvoice(req *lnmux_proto.SubscribeSingleInvoiceR } err := subscription.Send(&lnmux_proto.SubscribeSingleInvoiceResponse{ - State: marshallInvoiceState(update.State), - CancelledReason: marshallCancelledReason(update.CancelledReason), + State: marshallInvoiceState(update.State), }) if err != nil { return err @@ -170,7 +144,6 @@ func (s *server) AddInvoice(ctx context.Context, // Create the invoice. expiry := time.Duration(req.ExpirySecs) * time.Second - expiryTime := time.Now().Add(expiry) invoice, preimage, err := s.creator.Create( req.AmtMsat, expiry, req.Description, descHash, finalCltvExpiry, ) @@ -180,20 +153,6 @@ func (s *server) AddInvoice(ctx context.Context, hash := preimage.Hash() - // Store invoice. - creationData := &persistence.InvoiceCreationData{ - InvoiceCreationData: invoice.InvoiceCreationData, - CreatedAt: invoice.CreationDate, - ID: int64(rand.Int31()), - PaymentRequest: invoice.PaymentRequest, - ExpiresAt: expiryTime, - } - - err = s.registry.NewInvoice(creationData) - if err != nil { - return nil, err - } - return &lnmux_proto.AddInvoiceResponse{ PaymentRequest: invoice.PaymentRequest, Hash: hash[:], diff --git a/invoice_creator.go b/invoice_creator.go index efc0d6c..3e9f99c 100644 --- a/invoice_creator.go +++ b/invoice_creator.go @@ -1,7 +1,7 @@ package lnmux import ( - "crypto/rand" + "encoding/binary" "time" "github.com/btcsuite/btcd/btcec/v2" @@ -17,6 +17,8 @@ import ( "github.com/bottlepay/lnmux/types" ) +var byteOrder = binary.BigEndian + const virtualChannel = 12345 type InvoiceCreatorConfig struct { @@ -65,6 +67,8 @@ func (c *InvoiceCreator) Create(amtMSat int64, expiry time.Duration, memo string, descHash *lntypes.Hash, cltvDelta uint64) ( *Invoice, lntypes.Preimage, error) { + creationDate := time.Now() + // Get features. featureMgr, err := feature.NewManager(feature.Config{}) if err != nil { @@ -77,11 +81,27 @@ func (c *InvoiceCreator) Create(amtMSat int64, expiry time.Duration, nodeSigner := netann.NewNodeSigner(nodeKeySigner) - paymentPreimage := &lntypes.Preimage{} - if _, err := rand.Read(paymentPreimage[:]); err != nil { + privKey, err := c.keyRing.DerivePrivKey(c.idKeyDesc) + if err != nil { return nil, lntypes.Preimage{}, err } - paymentHash := paymentPreimage.Hash() + + expiryTime := creationDate.Add(expiry) + statelessData, err := encodeStatelessData( + privKey.Serialize(), amtMSat, expiryTime, + ) + if err != nil { + return nil, lntypes.Preimage{}, err + } + + paymentHash := statelessData.preimage.Hash() + + // TODO: Optionally we could encrypt the payment metadata here, just like + // rust-lightning does: + // https://github.com/lightningdevkit/rust-lightning/blob/a600eee87c96ee8865402e86bb1865011bf2d2de/lightning/src/ln/inbound_payment.rs#L166 + // + // Background: + // https://github.com/lightningdevkit/rust-lightning/issues/1171#issuecomment-1162817360 // We also create an encoded payment request which allows the // caller to compactly send the invoice to the payer. We'll create a @@ -128,17 +148,11 @@ func (c *InvoiceCreator) Create(amtMSat int64, expiry time.Duration, invoiceFeatures := featureMgr.Get(feature.SetInvoice) options = append(options, zpay32.Features(invoiceFeatures)) - // Generate and set a random payment address for this invoice. If the - // sender understands payment addresses, this can be used to avoid - // intermediaries probing the receiver. - var paymentAddr [32]byte - if _, err := rand.Read(paymentAddr[:]); err != nil { - return nil, lntypes.Preimage{}, err - } - options = append(options, zpay32.PaymentAddr(paymentAddr)) + // Set the payment address. + options = append(options, zpay32.PaymentAddr(statelessData.paymentAddr)) // Create and encode the payment request as a bech32 (zpay32) string. - creationDate := time.Now() + payReq, err := zpay32.NewInvoice( c.activeNetParams, paymentHash, creationDate, options..., ) @@ -159,12 +173,11 @@ func (c *InvoiceCreator) Create(amtMSat int64, expiry time.Duration, CreationDate: creationDate, PaymentRequest: payReqString, InvoiceCreationData: types.InvoiceCreationData{ - FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()), Value: lnwire.MilliSatoshi(amtMSat), - PaymentPreimage: *paymentPreimage, - PaymentAddr: paymentAddr, + PaymentPreimage: statelessData.preimage, + PaymentAddr: statelessData.paymentAddr, }, } - return newInvoice, *paymentPreimage, nil + return newInvoice, statelessData.preimage, nil } diff --git a/invoiceregistry.go b/invoiceregistry.go index 1f5ced6..1db2492 100644 --- a/invoiceregistry.go +++ b/invoiceregistry.go @@ -66,6 +66,10 @@ type RegistryConfig struct { Logger *zap.SugaredLogger + // PrivKey is the private key that is used for calculating payment metadata + // hmacs, which serve as the payment preimage. + PrivKey [32]byte + AutoSettle bool } @@ -74,6 +78,7 @@ type InvoiceCallback func(update InvoiceUpdate) type invoiceState struct { invoice *types.InvoiceCreationData acceptedHtlcs map[types.CircuitKey]*InvoiceHTLC + expiry time.Time } func (i *invoiceState) totalSetAmt() int { @@ -121,7 +126,6 @@ type InvoiceRegistry struct { invoices map[lntypes.Hash]*invoiceState htlcChan chan *registryHtlc - newInvoiceChan chan *persistence.InvoiceCreationData requestSettleChan chan *invoiceRequest cancelChan chan *invoiceRequest @@ -149,7 +153,6 @@ func NewRegistry(cdb *persistence.PostgresPersister, cfg: cfg, invoices: make(map[lntypes.Hash]*invoiceState), htlcChan: make(chan *registryHtlc), - newInvoiceChan: make(chan *persistence.InvoiceCreationData), newInvoiceSubscription: make(chan invoiceSubscription), cancelInvoiceSubscription: make(chan invoiceSubscriptionCancelRequest), subscriptionManager: newSubscriptionManager(cfg.Logger), @@ -166,44 +169,7 @@ func NewRegistry(cdb *persistence.PostgresPersister, func (i *InvoiceRegistry) Run(ctx context.Context) error { i.logger.Info("InvoiceRegistry starting") - pendingInvoices, err := i.cdb.GetOpen(ctx) - if err != nil { - return err - } - - i.logger.Infow("Open invoices", "count", len(pendingInvoices)) - - for _, invoice := range pendingInvoices { - // Immediately fail invoices that expired while we were not running. - if time.Now().After(invoice.ExpiresAt) { - hash := invoice.PaymentPreimage.Hash() - - i.logger.Debugw("Invoice expired", - "hash", hash, "expiresAt", invoice.ExpiresAt) - - err := i.cdb.Fail(ctx, hash, persistence.CancelledReasonExpired) - if err != nil { - return err - } - - continue - } - - state := &invoiceState{ - invoice: &invoice.InvoiceCreationData.InvoiceCreationData, - acceptedHtlcs: make(map[types.CircuitKey]*InvoiceHTLC), - } - - hash := invoice.PaymentPreimage.Hash() - - i.invoices[hash] = state - i.startInvoiceExpireTimer(hash, invoice.ExpiresAt) - - i.logger.Debugw("Pending invoice", - "hash", hash, "expiresAt", invoice.ExpiresAt) - } - - err = i.invoiceEventLoop(ctx) + err := i.invoiceEventLoop(ctx) if err != nil && !errors.Is(err, context.Canceled) { i.logger.Errorw("InvoiceRegistry error", "err", err) @@ -248,16 +214,6 @@ func (i *InvoiceRegistry) Subscribe(hash lntypes.Hash, }, nil } -func (i *InvoiceRegistry) NewInvoice(invoice *persistence.InvoiceCreationData) error { - select { - case i.newInvoiceChan <- invoice: - case <-i.quit: - return ErrShuttingDown - } - - return nil -} - func (i *InvoiceRegistry) RequestSettle(hash lntypes.Hash) error { i.logger.Debugw("New settle request received", "hash", hash) @@ -349,17 +305,9 @@ func (i *InvoiceRegistry) invoiceEventLoop(ctx context.Context) error { i.logger.Errorf("HTLC timer: %v", err) } - case *invoiceExpiredEvent: - err := i.failInvoice( - ctx, event.hash, persistence.CancelledReasonExpired, - ) - if err != nil { - return err - } - case *acceptTimeoutEvent: err := i.failInvoice( - ctx, event.hash, persistence.CancelledReasonAcceptTimeout, + ctx, event.hash, ) if err != nil { return err @@ -372,31 +320,6 @@ func (i *InvoiceRegistry) invoiceEventLoop(ctx context.Context) error { i.logger.Errorf("Process: %v", err) } - case invoice := <-i.newInvoiceChan: - err := i.cdb.Add(ctx, invoice) - if err != nil { - return err - } - - hash := invoice.PaymentPreimage.Hash() - - i.logger.Debugw("New invoice", - "hash", hash, "amt", invoice.Value) - - state := &invoiceState{ - invoice: &invoice.InvoiceCreationData, - acceptedHtlcs: make(map[types.CircuitKey]*InvoiceHTLC), - } - - i.invoices[hash] = state - - // Notify subscriber of new invoice. - i.subscriptionManager.notifySubscribers(hash, InvoiceUpdate{ - State: persistence.InvoiceStateOpen, - }) - - i.startInvoiceExpireTimer(hash, invoice.ExpiresAt) - case newSubscription := <-i.newInvoiceSubscription: if err := i.addSubscriber(ctx, newSubscription); err != nil { return err @@ -463,11 +386,6 @@ func (i *InvoiceRegistry) invoiceEventLoop(ctx context.Context) error { break } - // Mark invoice as failed. - if err := i.cdb.Fail(ctx, req.hash, persistence.CancelledReasonExternal); err != nil { - return errors.New("cannot fail invoice in database") - } - // Delete in-memory record for this invoice. Only open invoices are // kept in memory. delete(i.invoices, req.hash) @@ -481,15 +399,6 @@ func (i *InvoiceRegistry) invoiceEventLoop(ctx context.Context) error { i.notifyHodlSubscribers(key, resolution) } - // Notify subscriber of settled invoice. - i.subscriptionManager.notifySubscribers( - req.hash, - InvoiceUpdate{ - State: persistence.InvoiceStateCancelled, - CancelledReason: persistence.CancelledReasonExternal, - }, - ) - // Send success response. err := i.sendResponse(req.errChan, nil) if err != nil { @@ -531,10 +440,12 @@ func (i *InvoiceRegistry) addSubscriber(ctx context.Context, invoiceState, ok := i.invoices[hash] if ok { - update.State = persistence.InvoiceStateOpen - if len(invoiceState.acceptedHtlcs) > 0 { - update.State = persistence.InvoiceStateAccepted + // No event for partially accepted invoices. + if !invoiceState.isSetComplete() { + return nil } + + update.State = persistence.InvoiceStateAccepted } else { // Send other states from database. invoice, _, err := i.cdb.Get(ctx, hash) @@ -549,8 +460,10 @@ func (i *InvoiceRegistry) addSubscriber(ctx context.Context, return err } - update.State = invoice.State - update.CancelledReason = invoice.CancelledReason + update.State = persistence.InvoiceStateSettleRequested + if invoice.Settled { + update.State = persistence.InvoiceStateSettled + } } newSubscription.callback(update) @@ -559,7 +472,7 @@ func (i *InvoiceRegistry) addSubscriber(ctx context.Context, } func (i *InvoiceRegistry) failInvoice(ctx context.Context, - hash lntypes.Hash, reason persistence.CancelledReason) error { + hash lntypes.Hash) error { logger := i.logger.With("hash", hash) @@ -571,53 +484,19 @@ func (i *InvoiceRegistry) failInvoice(ctx context.Context, return nil } - // Don't expire invoices that are already accepted. - setComplete := state.isSetComplete() - if reason == persistence.CancelledReasonExpired && setComplete { - return nil - } - // Cancel all accepted htlcs. for key := range state.acceptedHtlcs { i.notifyHodlSubscribers(key, NewFailResolution(ResultInvoiceExpired)) } - // Mark invoice as expired in the database. - err := i.cdb.Fail(ctx, hash, reason) - if err != nil { - return err - } - // Remove from memory because invoice is no longer open. delete(i.invoices, hash) - // Notify subscriber. - i.subscriptionManager.notifySubscribers(hash, InvoiceUpdate{ - State: persistence.InvoiceStateCancelled, - CancelledReason: reason, - }) - logger.Infow("Failed invoice") return nil } -func (i *InvoiceRegistry) startInvoiceExpireTimer(hash lntypes.Hash, - releaseTime time.Time) { - - event := &invoiceExpiredEvent{ - eventBase: eventBase{ - hash: hash, - releaseTime: releaseTime, - }, - } - - i.logger.Debugw("Scheduling auto-release for invoice", - "hash", hash, "releaseTime", releaseTime) - - i.autoReleaseHeap.Push(event) -} - func (i *InvoiceRegistry) startAcceptTimer(hash lntypes.Hash) { releaseTime := time.Now().Add(i.cfg.AcceptTimeout) event := &acceptTimeoutEvent{ @@ -636,9 +515,8 @@ func (i *InvoiceRegistry) startAcceptTimer(hash lntypes.Hash) { // startHtlcTimer starts a new timer via the invoice registry main loop that // cancels a single htlc on an invoice when the htlc hold duration has passed. func (i *InvoiceRegistry) startHtlcTimer(hash lntypes.Hash, - key types.CircuitKey, acceptTime time.Time) { + key types.CircuitKey, releaseTime time.Time) { - releaseTime := acceptTime.Add(i.cfg.HtlcHoldDuration) event := &htlcReleaseEvent{ eventBase: eventBase{ hash: hash, @@ -686,6 +564,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(hash lntypes.Hash, delete(invoice.acceptedHtlcs, key) + // If this was the last htlc, clean up the in-memory record. + if len(invoice.acceptedHtlcs) == 0 { + delete(i.invoices, hash) + } + i.notifyHodlSubscribers(key, NewFailResolution(result)) return nil @@ -760,6 +643,17 @@ func (i *InvoiceRegistry) resolveViaDb(ctx context.Context, } func (i *InvoiceRegistry) process(ctx context.Context, h *registryHtlc) error { + logger := i.logger.With("hash", h.rHash) + + // First try to resolve via the database, in case this is a replay. + resolved, err := i.resolveViaDb(ctx, h) + if err != nil { + return err + } + if resolved { + return nil + } + // Always require an mpp record. mpp := h.payload.MultiPath() if mpp == nil { @@ -770,22 +664,66 @@ func (i *InvoiceRegistry) process(ctx context.Context, h *registryHtlc) error { return nil } + statelessData, err := decodeStatelessData( + i.cfg.PrivKey[:], mpp.PaymentAddr(), + ) + if err != nil { + return err + } + + // If the preimage doesn't match the payment hash, fail this htlc. Someone + // must have tampered with our payment parameters. + if statelessData.preimage.Hash() != h.rHash { + logger.Debugw("Hash mismatch") + + h.resolve(NewFailResolution(ResultInvoiceNotFound)) + + return nil + } + + logger = logger.With( + "amtMsat", statelessData.amtMsat, "expiry", statelessData.expiry, + ) + + // Check expiry. + if statelessData.expiry.Before(time.Now()) { + logger.Infow("Stateless invoice payment to expired invoice") + + h.resolve(NewFailResolution(ResultInvoiceExpired)) + + return nil + } + + logger.Infow("Stateless invoice payment received") + + // Look up this invoice in memory. If it is present, we have already + // received other shards of the payment. state, ok := i.invoices[h.rHash] if !ok { - // Invoice is not present in memory. Do a db lookup to see if this - // happens to be a previously settled invoice and resolve the htlc if - // possible. - resolved, err := i.resolveViaDb(ctx, h) - if err != nil { - return err + state = &invoiceState{ + invoice: &types.InvoiceCreationData{ + PaymentPreimage: statelessData.preimage, + Value: lnwire.MilliSatoshi(statelessData.amtMsat), + PaymentAddr: statelessData.paymentAddr, + }, + acceptedHtlcs: make(map[types.CircuitKey]*InvoiceHTLC), + expiry: statelessData.expiry, } - if !resolved { - // If the invoice was not found, return a failure - // resolution with an invoice not found result. + + i.invoices[h.rHash] = state + } else { + // Sanity check that the total amount and expiry time are identical. + if statelessData.amtMsat != int64(state.invoice.Value) || + statelessData.expiry != state.expiry { + + logger.Errorw("Stateless invoice sanity check failed", + "expectedAmtMsat", state.invoice.Value, "amtMsat", statelessData.amtMsat, + "expectedExpiry", state.expiry, "expiry", statelessData.expiry) + h.resolve(NewFailResolution(ResultInvoiceNotFound)) - } - return nil + return nil + } } if _, ok := state.acceptedHtlcs[h.circuitKey]; ok { @@ -860,14 +798,6 @@ func (i *InvoiceRegistry) process(ctx context.Context, h *registryHtlc) error { return nil } - if h.expiry < uint32(h.currentHeight+inv.FinalCltvDelta) { - h.resolve(NewFailResolution( - ResultExpiryTooSoon, - )) - - return nil - } - state.acceptedHtlcs[h.circuitKey] = &InvoiceHTLC{ Amt: h.amtPaid, MppTotalAmt: mpp.TotalMsat(), @@ -881,13 +811,19 @@ func (i *InvoiceRegistry) process(ctx context.Context, h *registryHtlc) error { // If the invoice cannot be settled yet, only record the htlc. setComplete := newSetTotal == mpp.TotalMsat() if !setComplete { - i.startHtlcTimer( - h.rHash, h.circuitKey, time.Now(), - ) + // Start a release timer for this htlc. We release either after the hold + // duration has passed or the invoice expires - whichever comes first. + releaseTime := time.Now().Add(i.cfg.HtlcHoldDuration) + if releaseTime.After(statelessData.expiry) { + releaseTime = statelessData.expiry + } + + i.startHtlcTimer(h.rHash, h.circuitKey, releaseTime) return nil } + // The set is complete and we start the accept timer. i.startAcceptTimer(h.rHash) // Notify subscriber of accepted invoice. @@ -912,8 +848,7 @@ func (i *InvoiceRegistry) process(ctx context.Context, h *registryHtlc) error { } type InvoiceUpdate struct { - State persistence.InvoiceState - CancelledReason persistence.CancelledReason + State persistence.InvoiceState } func (i *InvoiceRegistry) requestSettle(ctx context.Context, @@ -971,6 +906,9 @@ func (i *InvoiceRegistry) markSettleRequested(ctx context.Context, return errors.New("set no longer complete") } + i.logger.Infow("Stateless invoice JIT insertion", + "hash", hash) + // Store settle request in database. This is important to prevent partial // settles after a restart. htlcMap := make(map[types.CircuitKey]int64) @@ -978,11 +916,17 @@ func (i *InvoiceRegistry) markSettleRequested(ctx context.Context, htlcMap[key] = int64(htlc.Amt) } - err := i.cdb.RequestSettle( - ctx, hash, htlcMap, - ) + invoice := &persistence.InvoiceCreationData{ + InvoiceCreationData: types.InvoiceCreationData{ + Value: state.invoice.Value, + PaymentPreimage: state.invoice.PaymentPreimage, + PaymentAddr: state.invoice.PaymentAddr, + }, + } + + err := i.cdb.RequestSettle(ctx, invoice, htlcMap) if err != nil { - return errors.New("cannot settle invoice in database") + return fmt.Errorf("cannot request settle in database: %w", err) } // Notify subscriber of settle request. diff --git a/invoiceregistry_test.go b/invoiceregistry_test.go index 5d4efe3..adf6ad7 100644 --- a/invoiceregistry_test.go +++ b/invoiceregistry_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" + "github.com/bottlepay/lnmux/common" "github.com/bottlepay/lnmux/persistence" "github.com/bottlepay/lnmux/test" - "github.com/bottlepay/lnmux/types" + "github.com/btcsuite/btcd/chaincfg" "github.com/go-pg/pg/v10" "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lntypes" @@ -28,6 +29,8 @@ type registryTestContext struct { logger *zap.SugaredLogger testAmt int64 + + creator *InvoiceCreator } func newRegistryTestContext(t *testing.T, autoSettle bool) *registryTestContext { @@ -35,12 +38,24 @@ func newRegistryTestContext(t *testing.T, autoSettle bool) *registryTestContext pg, db := setupTestDB(t) + keyRing := NewKeyRing(testKey) + + creator, err := NewInvoiceCreator( + &InvoiceCreatorConfig{ + KeyRing: keyRing, + GwPubKeys: []common.PubKey{testPubKey1, testPubKey2}, + ActiveNetParams: &chaincfg.RegressionNetParams, + }, + ) + require.NoError(t, err) + cfg := &RegistryConfig{ Clock: clock.NewDefaultClock(), FinalCltvRejectDelta: 10, HtlcHoldDuration: time.Second, AcceptTimeout: time.Second * 2, Logger: logger.Sugar(), + PrivKey: testKey, AutoSettle: autoSettle, } @@ -51,6 +66,7 @@ func newRegistryTestContext(t *testing.T, autoSettle bool) *registryTestContext db: db, logger: cfg.Logger, testAmt: 10000, + creator: creator, } c.start() @@ -85,37 +101,18 @@ func (r *registryTestContext) close() { r.pg.Close() } -func (r *registryTestContext) preimage(id int) lntypes.Preimage { - return lntypes.Preimage{byte(id)} -} +func (r *registryTestContext) createInvoice(id int, expiry time.Duration) ( + *Invoice, lntypes.Preimage) { -func (r *registryTestContext) payAddr(id int) lntypes.Preimage { - return [32]byte{0, byte(id)} -} + invoice, preimage, err := r.creator.Create(r.testAmt, expiry, "test", nil, 40) + require.NoError(r.t, err) -func (r *registryTestContext) addInvoice(id int, expiry time.Duration) { - preimage := r.preimage(id) - payAddr := r.payAddr(id) - - require.NoError(r.t, r.registry.NewInvoice(&persistence.InvoiceCreationData{ - ExpiresAt: time.Now().Add(expiry), - InvoiceCreationData: types.InvoiceCreationData{ - FinalCltvDelta: 40, - PaymentPreimage: preimage, - Value: lnwire.MilliSatoshi(r.testAmt), - PaymentAddr: payAddr, - }, - CreatedAt: time.Now(), - PaymentRequest: "payreq", - ID: int64(id), - })) + return invoice, preimage } -func (r *registryTestContext) subscribe(id int) (chan InvoiceUpdate, func()) { - preimage := r.preimage(id) - +func (r *registryTestContext) subscribe(hash lntypes.Hash) (chan InvoiceUpdate, func()) { updateChan := make(chan InvoiceUpdate) - cancel, err := r.registry.Subscribe(preimage.Hash(), func(update InvoiceUpdate) { + cancel, err := r.registry.Subscribe(hash, func(update InvoiceUpdate) { updateChan <- update }) require.NoError(r.t, err) @@ -128,52 +125,31 @@ func TestInvoiceExpiry(t *testing.T) { c := newRegistryTestContext(t, false) - // Subscribe to updates for invoice 1. - updateChan1, cancel1 := c.subscribe(1) - // Add invoice. - c.addInvoice(1, time.Second) - - // Expect an open notification. - update := <-updateChan1 - require.Equal(t, persistence.InvoiceStateOpen, update.State) - - // Expected an expired notification. - update = <-updateChan1 - require.Equal(t, persistence.InvoiceStateCancelled, update.State) - require.Equal(t, persistence.CancelledReasonExpired, update.CancelledReason) - - cancel1() - - // Add another invoice. - c.addInvoice(2, time.Second) - - // Expect the open update. - updateChan2, cancel2 := c.subscribe(2) - update = <-updateChan2 - require.Equal(t, persistence.InvoiceStateOpen, update.State) - cancel2() - - // Stop the registry. - c.stop() + invoice, preimage := c.createInvoice(1, 100*time.Millisecond) // Wait for the invoice to expire. - time.Sleep(2 * time.Second) - - // Restart the registry. - c.start() + time.Sleep(200 * time.Millisecond) - // This should result in an immediate expiry of the invoice. - updateChan3, cancel3 := c.subscribe(2) - - select { - case update := <-updateChan3: - require.Equal(t, persistence.InvoiceStateCancelled, update.State) - require.Equal(t, persistence.CancelledReasonExpired, update.CancelledReason) + // Send htlc. + resolved := make(chan HtlcResolution) + c.registry.NotifyExitHopHtlc(®istryHtlc{ + rHash: preimage.Hash(), + amtPaid: lnwire.MilliSatoshi(c.testAmt), + expiry: 100, + currentHeight: 0, + resolve: func(r HtlcResolution) { + resolved <- r + }, + payload: &testPayload{ + amt: lnwire.MilliSatoshi(c.testAmt), + payAddr: invoice.PaymentAddr, + }, + }) - case <-time.After(200 * time.Millisecond): - } - cancel3() + resolution := <-resolved + require.IsType(t, &HtlcFailResolution{}, resolution) + require.Equal(t, ResultInvoiceExpired, resolution.(*HtlcFailResolution).Outcome) } func TestAutoSettle(t *testing.T) { @@ -181,17 +157,12 @@ func TestAutoSettle(t *testing.T) { c := newRegistryTestContext(t, true) - // Subscribe to updates for invoice 1. - updateChan, cancelUpdates := c.subscribe(1) - // Add invoice. - c.addInvoice(1, time.Hour) + invoice, preimage := c.createInvoice(1, time.Hour) - // Expect an open notification. - update := <-updateChan - require.Equal(t, persistence.InvoiceStateOpen, update.State) + // Subscribe to updates for invoice. + updateChan, cancelUpdates := c.subscribe(preimage.Hash()) - preimage := c.preimage(1) resolved := make(chan struct{}) c.registry.NotifyExitHopHtlc(®istryHtlc{ rHash: preimage.Hash(), @@ -203,11 +174,11 @@ func TestAutoSettle(t *testing.T) { }, payload: &testPayload{ amt: lnwire.MilliSatoshi(c.testAmt), - payAddr: c.payAddr(1), + payAddr: invoice.PaymentAddr, }, }) - update = <-updateChan + update := <-updateChan require.Equal(t, persistence.InvoiceStateAccepted, update.State) update = <-updateChan diff --git a/mux_test.go b/mux_test.go index 0e331ed..33029d4 100644 --- a/mux_test.go +++ b/mux_test.go @@ -23,11 +23,19 @@ import ( "github.com/bottlepay/lnmux/lnd" "github.com/bottlepay/lnmux/persistence" "github.com/bottlepay/lnmux/persistence/test" + test_common "github.com/bottlepay/lnmux/test" ) var ( testPubKey1, _ = common.NewPubKeyFromStr("02e1ce77dfdda9fd1cf5e9d796faf57d1cedef9803aec84a6d7f8487d32781341e") testPubKey2, _ = common.NewPubKeyFromStr("0314aaf9b2547682b81977b3ac0c5585c3521a0a5430fb410cb572d5c72364edf3") + + testKey = [32]byte{ + 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x68, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0xd, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1e, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, + } ) func createTestLndClient(ctrl *gomock.Controller, pubKey common.PubKey) ( @@ -62,14 +70,9 @@ func createTestLndClient(ctrl *gomock.Controller, pubKey common.PubKey) ( } func TestMux(t *testing.T) { - logger, _ := zap.NewDevelopment() + defer test_common.Timeout()() - var testKey = [32]byte{ - 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x68, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0xd, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, - 0x1e, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, - } + logger, _ := zap.NewDevelopment() keyRing := NewKeyRing(testKey) @@ -109,6 +112,7 @@ func TestMux(t *testing.T) { HtlcHoldDuration: time.Second, AcceptTimeout: time.Second * 2, Logger: logger.Sugar(), + PrivKey: testKey, }, ) @@ -129,15 +133,6 @@ func TestMux(t *testing.T) { errChan <- mux.Run(ctx) }() - // Store invoice. - require.NoError(t, registry.NewInvoice(&persistence.InvoiceCreationData{ - ExpiresAt: time.Now().Add(time.Minute), - InvoiceCreationData: invoice.InvoiceCreationData, - CreatedAt: time.Now(), - PaymentRequest: "payreq", - ID: 1, - })) - var updateChan = make(chan InvoiceUpdate, 1) cancelSubscription, err := registry.Subscribe(testHash, func(update InvoiceUpdate) { logger.Sugar().Infow("Payment received", "state", update.State) @@ -152,8 +147,6 @@ func TestMux(t *testing.T) { require.Equal(t, state, update.State) } - expectUpdate(persistence.InvoiceStateOpen) - // Send initial block heights. blockChan1 <- &chainrpc.BlockEpoch{Height: 1000} blockChan2 <- &chainrpc.BlockEpoch{Height: 1000} @@ -198,10 +191,11 @@ func TestMux(t *testing.T) { } expectResponse := func(resp *routerrpc.ForwardHtlcInterceptResponse, - expectedAction routerrpc.ResolveHoldForwardAction) { + htlcID int, expectedAction routerrpc.ResolveHoldForwardAction) { t.Helper() + require.Equal(t, uint64(htlcID), resp.IncomingCircuitKey.HtlcId) require.Equal(t, expectedAction, resp.Action) } @@ -213,8 +207,8 @@ func TestMux(t *testing.T) { htlcChan1 <- receiveHtlc(0, 6000) // Let it time out. Expect two responses, one for each notified arrival. - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_FAIL) - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_FAIL) + expectResponse(<-responseChan1, 0, routerrpc.ResolveHoldForwardAction_FAIL) + expectResponse(<-responseChan1, 0, routerrpc.ResolveHoldForwardAction_FAIL) // Notify arrival of part 1. htlcChan1 <- receiveHtlc(1, 6000) @@ -227,8 +221,8 @@ func TestMux(t *testing.T) { expectUpdate(persistence.InvoiceStateSettleRequested) - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_SETTLE) - expectResponse(<-responseChan2, routerrpc.ResolveHoldForwardAction_SETTLE) + expectResponse(<-responseChan1, 1, routerrpc.ResolveHoldForwardAction_SETTLE) + expectResponse(<-responseChan2, 2, routerrpc.ResolveHoldForwardAction_SETTLE) expectUpdate(persistence.InvoiceStateSettled) @@ -238,11 +232,11 @@ func TestMux(t *testing.T) { // Replay settled htlc. htlcChan1 <- receiveHtlc(1, 6000) - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_SETTLE) + expectResponse(<-responseChan1, 1, routerrpc.ResolveHoldForwardAction_SETTLE) // New payment to settled invoice htlcChan1 <- receiveHtlc(10, 10000) - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_FAIL) + expectResponse(<-responseChan1, 10, routerrpc.ResolveHoldForwardAction_FAIL) cancelSubscription() @@ -262,16 +256,6 @@ func TestMux(t *testing.T) { }) require.NoError(t, err) - // Store invoice. - require.NoError(t, registry.NewInvoice(&persistence.InvoiceCreationData{ - InvoiceCreationData: invoice.InvoiceCreationData, - CreatedAt: time.Now(), - PaymentRequest: "payreq", - ID: 2, - ExpiresAt: time.Now().Add(time.Minute), - })) - expectUpdate(persistence.InvoiceStateOpen) - // Regenerate onion blob for new hash. onionBlob = genOnion() @@ -282,7 +266,7 @@ func TestMux(t *testing.T) { expectUpdate(persistence.InvoiceStateSettleRequested) - expectResponse(<-responseChan1, routerrpc.ResolveHoldForwardAction_SETTLE) + expectResponse(<-responseChan1, 20, routerrpc.ResolveHoldForwardAction_SETTLE) expectUpdate(persistence.InvoiceStateSettled) diff --git a/persistence/migrations/1_initial.up.sql b/persistence/migrations/1_initial.up.sql index 1b88679..6b37235 100644 --- a/persistence/migrations/1_initial.up.sql +++ b/persistence/migrations/1_initial.up.sql @@ -1,35 +1,15 @@ SET search_path TO lnmux; -CREATE TYPE invoice_state as ENUM ( - 'OPEN', - 'SETTLE_REQUESTED', - 'SETTLED', - 'CANCELLED' -); - -CREATE TYPE cancelled_reason as ENUM ( - 'EXPIRED', - 'ACCEPT_TIMEOUT', - 'EXTERNAL' -); - CREATE TABLE invoices ( - "created_at" TIMESTAMPTZ NOT NULL, - "settled_at" TIMESTAMPTZ, -- TODO: rename to completed_at + "settled_at" TIMESTAMPTZ, "settle_requested_at" TIMESTAMPTZ, - "hash" BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(hash) = 32), + "hash" BYTEA NOT NULL CHECK (LENGTH(hash) = 32), "preimage" BYTEA NOT NULL UNIQUE CHECK (LENGTH(preimage) = 32), "amount_msat" BIGINT NOT NULL CHECK (amount_msat > 0), - "id" BIGINT NOT NULL UNIQUE CHECK (id > 0), - "expires_at" TIMESTAMPTZ NOT NULL, - - "state" invoice_state NOT NULL, - "cancelled_reason" cancelled_reason, -- TODO: Add check for state CANCELLED + "settled" BOOLEAN NOT NULL, - "final_cltv_delta" INTEGER NOT NULL CHECK (final_cltv_delta > 0), - "payment_addr" BYTEA NOT NULL UNIQUE CHECK (LENGTH(payment_addr) = 32), - "payment_request" TEXT NOT NULL CHECK (LENGTH(payment_request) > 0) + PRIMARY KEY (hash) ); CREATE TABLE htlcs diff --git a/persistence/pg.go b/persistence/pg.go index 04d4ef3..2b89194 100644 --- a/persistence/pg.go +++ b/persistence/pg.go @@ -16,9 +16,7 @@ import ( type Invoice struct { InvoiceCreationData - State InvoiceState - CancelledReason CancelledReason - + Settled bool SettledAt time.Time SettleRequestedAt time.Time } @@ -26,66 +24,25 @@ type Invoice struct { type InvoiceState int const ( - InvoiceStateOpen InvoiceState = iota - InvoiceStateAccepted // This state is not persisted in the database. + InvoiceStateAccepted InvoiceState = iota InvoiceStateSettleRequested InvoiceStateSettled - InvoiceStateCancelled -) - -type CancelledReason int - -const ( - CancelledReasonNone CancelledReason = iota - CancelledReasonExpired - CancelledReasonAcceptTimeout - CancelledReasonExternal ) type InvoiceCreationData struct { types.InvoiceCreationData - - CreatedAt time.Time - ExpiresAt time.Time - ID int64 - PaymentRequest string } -type dbInvoiceState string - -const ( - dbInvoiceStateOpen = dbInvoiceState("OPEN") - dbInvoiceStateSettleRequested = dbInvoiceState("SETTLE_REQUESTED") - dbInvoiceStateSettled = dbInvoiceState("SETTLED") - dbInvoiceStateExpired = dbInvoiceState("EXPIRED") - dbInvoiceStateCancelled = dbInvoiceState("CANCELLED") -) - -type dbCancelledReason string - -const ( - dbCancelledReasonExpired = dbCancelledReason("EXPIRED") - dbCancelledReasonAcceptTimeout = dbCancelledReason("ACCEPT_TIMEOUT") - dbCancelledReasonExternal = dbCancelledReason("EXTERNAL") -) - type dbInvoice struct { tableName struct{} `pg:"lnmux.invoices,discard_unknown_columns"` // nolint Hash lntypes.Hash `pg:"hash"` Preimage lntypes.Preimage `pg:"preimage"` - CreatedAt time.Time `pg:"created_at"` - ExpiresAt time.Time `pg:"expires_at"` AmountMsat int64 `pg:"amount_msat,use_zero"` - ID int64 `pg:"id,use_zero"` - - State dbInvoiceState `pg:"state"` - CancelledReason *dbCancelledReason `pg:"cancelled_reason"` - SettledAt time.Time `pg:"settled_at"` - SettleRequestedAt time.Time `pg:"settle_requested_at"` - FinalCltvDelta int32 `pg:"final_cltv_delta,use_zero"` - PaymentAddr [32]byte `pg:"payment_addr"` - PaymentRequest string `pg:"payment_request"` + + Settled bool `pg:"settled,use_zero"` + SettledAt time.Time `pg:"settled_at"` + SettleRequestedAt time.Time `pg:"settle_requested_at"` } type dbHtlc struct { @@ -119,84 +76,16 @@ func (p *PostgresPersister) Delete(ctx context.Context, hash lntypes.Hash) error return nil } -func unmarshallDbInvoiceState(state dbInvoiceState) InvoiceState { - switch state { - case dbInvoiceStateOpen: - return InvoiceStateOpen - - case dbInvoiceStateSettleRequested: - return InvoiceStateSettleRequested - - case dbInvoiceStateSettled: - return InvoiceStateSettled - - case dbInvoiceStateCancelled: - return InvoiceStateCancelled - - default: - panic("unknown invoice state") - } -} - -func unmarshallDbCancelledReason(reason *dbCancelledReason) CancelledReason { - if reason == nil { - return CancelledReasonNone - } - - switch *reason { - case dbCancelledReasonExpired: - return CancelledReasonExpired - - case dbCancelledReasonAcceptTimeout: - return CancelledReasonAcceptTimeout - - case dbCancelledReasonExternal: - return CancelledReasonExternal - - default: - panic("unknown cancelled reason") - } -} - -func marshallCancelledReason(reason CancelledReason) *dbCancelledReason { - var dbReason dbCancelledReason - switch reason { - case CancelledReasonNone: - return nil - - case CancelledReasonExpired: - dbReason = dbCancelledReasonExpired - - case CancelledReasonAcceptTimeout: - dbReason = dbCancelledReasonAcceptTimeout - - case CancelledReasonExternal: - dbReason = dbCancelledReasonExternal - - default: - panic("unknown cancelled reason") - } - - return &dbReason -} - func unmarshallDbInvoice(invoice *dbInvoice) *Invoice { return &Invoice{ InvoiceCreationData: InvoiceCreationData{ - CreatedAt: invoice.CreatedAt, - PaymentRequest: invoice.PaymentRequest, InvoiceCreationData: types.InvoiceCreationData{ - FinalCltvDelta: invoice.FinalCltvDelta, PaymentPreimage: invoice.Preimage, Value: lnwire.MilliSatoshi(invoice.AmountMsat), - PaymentAddr: invoice.PaymentAddr, }, - ID: invoice.ID, - ExpiresAt: invoice.ExpiresAt, }, - SettledAt: invoice.SettledAt, - State: unmarshallDbInvoiceState(invoice.State), - CancelledReason: unmarshallDbCancelledReason(invoice.CancelledReason), + SettledAt: invoice.SettledAt, + Settled: invoice.Settled, } } @@ -234,46 +123,25 @@ func (p *PostgresPersister) Get(ctx context.Context, hash lntypes.Hash) (*Invoic return invoice, htlcs, nil } -func (p *PostgresPersister) GetOpen(ctx context.Context) ([]*Invoice, - error) { - - var dbInvoices []dbInvoice - err := p.conn.ModelContext(ctx, &dbInvoices). - Where("state=?", dbInvoiceStateOpen).Select() - - if err != nil { - return nil, err - } - - var invoices []*Invoice - for _, dbInvoice := range dbInvoices { - invoice := unmarshallDbInvoice(&dbInvoice) - invoices = append(invoices, invoice) - } - - return invoices, nil -} - func (p *PostgresPersister) RequestSettle(ctx context.Context, - hash lntypes.Hash, htlcs map[types.CircuitKey]int64) error { + invoice *InvoiceCreationData, htlcs map[types.CircuitKey]int64) error { return p.conn.RunInTransaction(ctx, func(tx *pg.Tx) error { - result, err := p.conn.ModelContext(ctx, &dbInvoice{}). - Set("state=?", dbInvoiceStateSettleRequested). - Set("settle_requested_at=?", time.Now().UTC()). - Where("hash=?", hash). - Where("state=?", dbInvoiceStateOpen). - Update() - if err != nil { - return fmt.Errorf("cannot request settle: %w", err) + dbInvoice := &dbInvoice{ + Hash: invoice.PaymentPreimage.Hash(), + Preimage: invoice.PaymentPreimage, + AmountMsat: int64(invoice.Value), + SettleRequestedAt: time.Now(), } - if result.RowsAffected() == 0 { - return errors.New("cannot request settle") + + _, err := p.conn.ModelContext(ctx, dbInvoice).Insert() + if err != nil { + return err } for key, amt := range htlcs { dbHtlc := dbHtlc{ - Hash: hash, + Hash: invoice.PaymentPreimage.Hash(), ChanID: key.ChanID, HtlcID: key.HtlcID, AmountMsat: amt, @@ -292,10 +160,10 @@ func (p *PostgresPersister) Settle(ctx context.Context, hash lntypes.Hash) error { result, err := p.conn.ModelContext(ctx, &dbInvoice{}). - Set("state=?", dbInvoiceStateSettled). + Set("settled=?", true). Set("settled_at=?", time.Now().UTC()). Where("hash=?", hash). - Where("state=?", dbInvoiceStateSettleRequested). + Where("settled=?", false). Update() if err != nil { return fmt.Errorf("cannot settle invoice: %w", err) @@ -307,49 +175,6 @@ func (p *PostgresPersister) Settle(ctx context.Context, return nil } -func (p *PostgresPersister) Fail(ctx context.Context, - hash lntypes.Hash, reason CancelledReason) error { - - if reason == CancelledReasonNone { - return errors.New("no cancelled reason specified") - } - - result, err := p.conn.ModelContext(ctx, &dbInvoice{}). - Set("state=?", dbInvoiceStateCancelled). - Set("settled_at=?", time.Now().UTC()). - Set("cancelled_reason=?", marshallCancelledReason(reason)). - Where("hash=?", hash). - Where("state=?", dbInvoiceStateOpen). - Update() - if err != nil { - return fmt.Errorf("cannot fail invoice: %w", err) - } - if result.RowsAffected() == 0 { - return errors.New("cannot fail invoice") - } - - return nil -} - -func (p *PostgresPersister) Add(ctx context.Context, invoice *InvoiceCreationData) error { - dbInvoice := &dbInvoice{ - CreatedAt: invoice.CreatedAt, - ExpiresAt: invoice.ExpiresAt, - Hash: invoice.PaymentPreimage.Hash(), - Preimage: invoice.PaymentPreimage, - AmountMsat: int64(invoice.Value), - FinalCltvDelta: invoice.FinalCltvDelta, - PaymentAddr: invoice.PaymentAddr, - PaymentRequest: invoice.PaymentRequest, - ID: invoice.ID, - State: dbInvoiceStateOpen, - } - - _, err := p.conn.ModelContext(ctx, dbInvoice).Insert() - - return err -} - // Ping pings the database connection to ensure it is available func (p *PostgresPersister) Ping(ctx context.Context) error { if p.conn != nil { diff --git a/persistence/pg_test.go b/persistence/pg_test.go index 3ed959e..ea2b520 100644 --- a/persistence/pg_test.go +++ b/persistence/pg_test.go @@ -3,7 +3,6 @@ package persistence import ( "context" "testing" - "time" "github.com/go-pg/pg/v10" "github.com/lightningnetwork/lnd/lntypes" @@ -37,26 +36,7 @@ func TestSettleInvoice(t *testing.T) { _, _, err := persister.Get(context.Background(), hash) require.ErrorIs(t, err, types.ErrInvoiceNotFound) - require.NoError(t, persister.Add(context.Background(), &InvoiceCreationData{ - CreatedAt: time.Unix(100, 0), - PaymentRequest: "ln...", - InvoiceCreationData: types.InvoiceCreationData{ - FinalCltvDelta: 40, - PaymentPreimage: preimage, - Value: 100, - PaymentAddr: [32]byte{2}, - }, - ID: 123, - ExpiresAt: time.Now().Add(time.Hour), - })) - - invoice, htlcs, err := persister.Get(context.Background(), hash) - require.NoError(t, err) - require.Empty(t, htlcs, 0) - require.Equal(t, invoice.PaymentRequest, "ln...") - require.Equal(t, InvoiceStateOpen, invoice.State) - - htlcs = map[types.CircuitKey]int64{ + htlcs := map[types.CircuitKey]int64{ { ChanID: 10, HtlcID: 11, @@ -66,6 +46,18 @@ func TestSettleInvoice(t *testing.T) { HtlcID: 12, }: 30, } - require.NoError(t, persister.RequestSettle(context.Background(), hash, htlcs)) + require.NoError(t, persister.RequestSettle(context.Background(), &InvoiceCreationData{ + InvoiceCreationData: types.InvoiceCreationData{ + PaymentPreimage: preimage, + Value: 100, + PaymentAddr: [32]byte{2}, + }, + }, htlcs)) + + invoice, htlcs, err := persister.Get(context.Background(), hash) + require.NoError(t, err) + require.Len(t, htlcs, 2) + require.False(t, invoice.Settled) + require.NoError(t, persister.Settle(context.Background(), hash)) } diff --git a/release_event.go b/release_event.go index 23be1be..0809cfb 100644 --- a/release_event.go +++ b/release_event.go @@ -39,10 +39,6 @@ func (r *eventBase) Less(other queue.PriorityQueueItem) bool { return r.getReleaseTime().Before(other.(releaseEvent).getReleaseTime()) } -type invoiceExpiredEvent struct { - eventBase -} - type acceptTimeoutEvent struct { eventBase } diff --git a/stateless_data.go b/stateless_data.go new file mode 100644 index 0000000..1f34148 --- /dev/null +++ b/stateless_data.go @@ -0,0 +1,83 @@ +package lnmux + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "time" + + "github.com/lightningnetwork/lnd/lntypes" +) + +type statelessData struct { + amtMsat int64 + expiry time.Time + preimage lntypes.Preimage + paymentAddr [32]byte +} + +func encodeStatelessData(privKey []byte, amtMsat int64, expiry time.Time) ( + *statelessData, error) { + + // We'll use part of the payment address to store the stateless invoice + // properties. + var addr [32]byte + + // Start payment address with 16 random bytes. The payment address was + // introduced to thwart probing attacks and 16 bytes still provide plenty of + // protection. + if _, err := rand.Read(addr[:16]); err != nil { + return nil, err + } + + // Store the expiry time in the payment address. + byteOrder.PutUint64(addr[16:24], uint64(expiry.Unix())) + + // Store amount in the payment address. + byteOrder.PutUint64(addr[24:32], uint64(amtMsat)) + + // Create hmac using private key. + mac := hmac.New(sha256.New, privKey) + mac.Write(addr[:]) + + // The hmac can only be derived with knowledge of the private key. We'll use + // it as the preimage for this invoice. When we later receive a payment, the + // preimage can be re-derived. + preimageBytes := mac.Sum(nil) + + paymentPreimage, err := lntypes.MakePreimage(preimageBytes) + if err != nil { + return nil, err + } + + return &statelessData{ + amtMsat: amtMsat, + expiry: expiry, + preimage: paymentPreimage, + paymentAddr: addr, + }, nil +} + +func decodeStatelessData(privKey []byte, paymentAddr [32]byte) ( + *statelessData, error) { + + // Re-derive preimage from payment address. + mac := hmac.New(sha256.New, privKey[:]) + + mac.Write(paymentAddr[:]) + paymentPreimage, err := lntypes.MakePreimage(mac.Sum(nil)) + if err != nil { + return nil, err + } + + // Extract payment parameters. + expiry := time.Unix(int64(byteOrder.Uint64(paymentAddr[16:24])), 0) + amtMsat := byteOrder.Uint64(paymentAddr[24:32]) + + return &statelessData{ + amtMsat: int64(amtMsat), + expiry: expiry, + paymentAddr: paymentAddr, + preimage: paymentPreimage, + }, nil +} diff --git a/test/timeout.go b/test/timeout.go index 92fb1c0..c0afc6f 100644 --- a/test/timeout.go +++ b/test/timeout.go @@ -6,7 +6,7 @@ import ( "time" ) -const testTimeout = 20 * time.Second +const testTimeout = 30 * time.Second // Timeout implements a test level timeout. func Timeout() func() { diff --git a/types/types.go b/types/types.go index f5c44c0..14d5570 100644 --- a/types/types.go +++ b/types/types.go @@ -14,10 +14,6 @@ var ( ) type InvoiceCreationData struct { - // FinalCltvDelta is the minimum required number of blocks before htlc - // expiry when the invoice is accepted. - FinalCltvDelta int32 - // PaymentPreimage is the preimage which is to be revealed in the // occasion that an HTLC paying to the hash of this preimage is // extended. Set to nil if the preimage isn't known yet.