diff --git a/lnrpc/invoicesrpc/invoice_acceptor.go b/lnrpc/invoicesrpc/invoice_acceptor.go new file mode 100644 index 000000000..3048a9e52 --- /dev/null +++ b/lnrpc/invoicesrpc/invoice_acceptor.go @@ -0,0 +1,111 @@ +package invoicesrpc + +import ( + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/lntypes" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// invoiceAcceptorConfig contains the configuration for an RPC invoice acceptor +// server. +type invoiceAcceptorConfig struct { + // chainParams is required to properly marshall an invoice for RPC. + chainParams *chaincfg.Params + + // rpcServer is a bidirectional RPC server to send invoices to a client + // and receive accept responses from the client. + rpcServer Invoices_InvoiceAcceptorServer + + // interceptor is the invoice interceptor that will be used to intercept + // and resolve invoices. + interceptor invoices.SettlementInterceptorInterface +} + +// invoiceAcceptor is a helper struct that handles the lifecycle of an RPC +// invoice acceptor server instance. +// +// This struct handles passing send and receive RPC messages between the client +// and the invoice service. +type invoiceAcceptor struct { + // cfg contains the configuration for the invoice acceptor. + cfg invoiceAcceptorConfig +} + +// newInvoiceAcceptor creates a new RPC invoice acceptor handler. +func newInvoiceAcceptor(cfg invoiceAcceptorConfig) *invoiceAcceptor { + return &invoiceAcceptor{ + cfg: cfg, + } +} + +// run sends the intercepted invoices to the client and receives the +// corresponding responses. +func (r *invoiceAcceptor) run() error { + // Register our invoice interceptor. + r.cfg.interceptor.SetClientCallback(r.onIntercept) + defer r.cfg.interceptor.SetClientCallback(nil) + + // Listen for a response from the client in a loop. + for { + resp, err := r.cfg.rpcServer.Recv() + if err != nil { + return err + } + + if err := r.resolveFromClient(resp); err != nil { + return err + } + } +} + +// onIntercept is called when an invoice is intercepted by the invoice +// interceptor. This method sends the invoice to the client. +func (r *invoiceAcceptor) onIntercept( + req invoices.InterceptClientRequest) error { + + // Convert the circuit key to an RPC circuit key. + rpcCircuitKey := &CircuitKey{ + ChanId: req.ExitHtlcCircuitKey.ChanID.ToUint64(), + HtlcId: req.ExitHtlcCircuitKey.HtlcID, + } + + // Convert the invoice to an RPC invoice. + rpcInvoice, err := CreateRPCInvoice(&req.Invoice, r.cfg.chainParams) + if err != nil { + return err + } + + return r.cfg.rpcServer.Send(&InvoiceAcceptorRequest{ + Invoice: rpcInvoice, + ExitHtlcCircuitKey: rpcCircuitKey, + ExitHtlcAmt: uint64(req.ExitHtlcAmt), + ExitHtlcExpiry: req.ExitHtlcExpiry, + CurrentHeight: req.CurrentHeight, + }) +} + +// resolveFromClient handles an invoice resolution received from the client. +func (r *invoiceAcceptor) resolveFromClient( + in *InvoiceAcceptorResponse) error { + + log.Tracef("Resolving invoice acceptor response %v", in) + + // Parse the invoice preimage from the response. + if len(in.Preimage) != lntypes.HashSize { + return status.Errorf(codes.InvalidArgument, + "Preimage has invalid length: %d", len(in.Preimage)) + } + preimage, err := lntypes.MakePreimage(in.Preimage) + if err != nil { + return status.Errorf(codes.InvalidArgument, + "Preimage is invalid: %v", err) + } + + // Derive the payment hash from the preimage. + paymentHash := preimage.Hash() + + // Pass the resolution to the interceptor. + return r.cfg.interceptor.Resolve(paymentHash, in.SkipAmountCheck) +} diff --git a/lnrpc/invoicesrpc/invoices.pb.go b/lnrpc/invoicesrpc/invoices.pb.go index 27cf424d3..e33f270a8 100644 --- a/lnrpc/invoicesrpc/invoices.pb.go +++ b/lnrpc/invoicesrpc/invoices.pb.go @@ -617,6 +617,212 @@ func (*LookupInvoiceMsg_PaymentAddr) isLookupInvoiceMsg_InvoiceRef() {} func (*LookupInvoiceMsg_SetId) isLookupInvoiceMsg_InvoiceRef() {} +// CircuitKey is a unique identifier for an HTLC. +type CircuitKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // / The id of the channel that the is part of this circuit. + ChanId uint64 `protobuf:"varint,1,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` + // / The index of the incoming htlc in the incoming channel. + HtlcId uint64 `protobuf:"varint,2,opt,name=htlc_id,json=htlcId,proto3" json:"htlc_id,omitempty"` +} + +func (x *CircuitKey) Reset() { + *x = CircuitKey{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CircuitKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CircuitKey) ProtoMessage() {} + +func (x *CircuitKey) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_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 CircuitKey.ProtoReflect.Descriptor instead. +func (*CircuitKey) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{8} +} + +func (x *CircuitKey) GetChanId() uint64 { + if x != nil { + return x.ChanId + } + return 0 +} + +func (x *CircuitKey) GetHtlcId() uint64 { + if x != nil { + return x.HtlcId + } + return 0 +} + +// InvoiceAcceptorRequest is a message that the server sends to the client to +// request the client to accept an invoice. +type InvoiceAcceptorRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // invoice is the invoice that the client should consider accepting. + Invoice *lnrpc.Invoice `protobuf:"bytes,1,opt,name=invoice,proto3" json:"invoice,omitempty"` + // exit_htlc_circuit_key is the key of the HTLC for which we is involved in + // the invoice settlement attempt. + ExitHtlcCircuitKey *CircuitKey `protobuf:"bytes,2,opt,name=exit_htlc_circuit_key,json=exitHtlcCircuitKey,proto3" json:"exit_htlc_circuit_key,omitempty"` + // exit_htlc_amt is the amount (millisats) that the client should consider + // accepting. + ExitHtlcAmt uint64 `protobuf:"varint,3,opt,name=exit_htlc_amt,json=exitHtlcAmt,proto3" json:"exit_htlc_amt,omitempty"` + // exit_htlc_expiry is the expiry time of the exit HTLC. + ExitHtlcExpiry uint32 `protobuf:"varint,4,opt,name=exit_htlc_expiry,json=exitHtlcExpiry,proto3" json:"exit_htlc_expiry,omitempty"` + // current_height is the current block height. + CurrentHeight uint32 `protobuf:"varint,5,opt,name=current_height,json=currentHeight,proto3" json:"current_height,omitempty"` +} + +func (x *InvoiceAcceptorRequest) Reset() { + *x = InvoiceAcceptorRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InvoiceAcceptorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InvoiceAcceptorRequest) ProtoMessage() {} + +func (x *InvoiceAcceptorRequest) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_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 InvoiceAcceptorRequest.ProtoReflect.Descriptor instead. +func (*InvoiceAcceptorRequest) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{9} +} + +func (x *InvoiceAcceptorRequest) GetInvoice() *lnrpc.Invoice { + if x != nil { + return x.Invoice + } + return nil +} + +func (x *InvoiceAcceptorRequest) GetExitHtlcCircuitKey() *CircuitKey { + if x != nil { + return x.ExitHtlcCircuitKey + } + return nil +} + +func (x *InvoiceAcceptorRequest) GetExitHtlcAmt() uint64 { + if x != nil { + return x.ExitHtlcAmt + } + return 0 +} + +func (x *InvoiceAcceptorRequest) GetExitHtlcExpiry() uint32 { + if x != nil { + return x.ExitHtlcExpiry + } + return 0 +} + +func (x *InvoiceAcceptorRequest) GetCurrentHeight() uint32 { + if x != nil { + return x.CurrentHeight + } + return 0 +} + +// InvoiceAcceptorResponse is a message that the client sends to the server to +// indicate whether it accepts the invoice or not. +type InvoiceAcceptorResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // preimage is the preimage of the invoice. + Preimage []byte `protobuf:"bytes,1,opt,name=preimage,proto3" json:"preimage,omitempty"` + // skip_amount_check is a flag that indicates whether the client wants to + // skip the amount check during the invoice settlement process. + SkipAmountCheck bool `protobuf:"varint,2,opt,name=skip_amount_check,json=skipAmountCheck,proto3" json:"skip_amount_check,omitempty"` +} + +func (x *InvoiceAcceptorResponse) Reset() { + *x = InvoiceAcceptorResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_invoicesrpc_invoices_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InvoiceAcceptorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InvoiceAcceptorResponse) ProtoMessage() {} + +func (x *InvoiceAcceptorResponse) ProtoReflect() protoreflect.Message { + mi := &file_invoicesrpc_invoices_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 InvoiceAcceptorResponse.ProtoReflect.Descriptor instead. +func (*InvoiceAcceptorResponse) Descriptor() ([]byte, []int) { + return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{10} +} + +func (x *InvoiceAcceptorResponse) GetPreimage() []byte { + if x != nil { + return x.Preimage + } + return nil +} + +func (x *InvoiceAcceptorResponse) GetSkipAmountCheck() bool { + if x != nil { + return x.SkipAmountCheck + } + return false +} + var File_invoicesrpc_invoices_proto protoreflect.FileDescriptor var file_invoicesrpc_invoices_proto_rawDesc = []byte{ @@ -678,41 +884,73 @@ var file_invoicesrpc_invoices_proto_rawDesc = []byte{ 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0e, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x72, - 0x65, 0x66, 0x2a, 0x44, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, - 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, - 0x4c, 0x59, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, - 0x5f, 0x42, 0x4c, 0x41, 0x4e, 0x4b, 0x10, 0x02, 0x32, 0x9b, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x56, 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, - 0x2a, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 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, 0x0e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, - 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, - 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, - 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, - 0x0e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, - 0x22, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, - 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, - 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x56, 0x32, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, - 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, - 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, - 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x65, 0x66, 0x22, 0x3e, 0x0a, 0x0a, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x17, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x74, 0x6c, + 0x63, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x74, 0x6c, 0x63, + 0x49, 0x64, 0x22, 0x83, 0x02, 0x0a, 0x16, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, + 0x07, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x07, + 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x15, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x63, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, 0x4b, 0x65, 0x79, 0x52, + 0x12, 0x65, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x69, 0x72, 0x63, 0x75, 0x69, 0x74, + 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0d, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x68, 0x74, 0x6c, 0x63, + 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x65, 0x78, 0x69, 0x74, + 0x48, 0x74, 0x6c, 0x63, 0x41, 0x6d, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0e, 0x65, 0x78, 0x69, 0x74, 0x48, 0x74, 0x6c, 0x63, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x61, 0x0a, 0x17, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x73, 0x6b, 0x69, 0x70, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x2a, 0x44, 0x0a, 0x0e, 0x4c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x0b, 0x0a, + 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x48, 0x54, + 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x01, 0x12, 0x12, 0x0a, + 0x0e, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x42, 0x4c, 0x41, 0x4e, 0x4b, 0x10, + 0x02, 0x32, 0xfd, 0x03, 0x0a, 0x08, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x56, + 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, 0x2a, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 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, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, + 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x55, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, + 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x22, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, 0x6c, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, + 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x48, 0x6f, + 0x6c, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, + 0x0d, 0x53, 0x65, 0x74, 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x1d, + 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, + 0x74, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, 0x1e, 0x2e, + 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x74, + 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x40, 0x0a, + 0x0f, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x56, 0x32, + 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x4c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x1a, + 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, + 0x60, 0x0a, 0x0f, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x6f, 0x72, 0x12, 0x24, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, + 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x23, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, + 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x73, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -728,7 +966,7 @@ func file_invoicesrpc_invoices_proto_rawDescGZIP() []byte { } var file_invoicesrpc_invoices_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_invoicesrpc_invoices_proto_goTypes = []interface{}{ (LookupModifier)(0), // 0: invoicesrpc.LookupModifier (*CancelInvoiceMsg)(nil), // 1: invoicesrpc.CancelInvoiceMsg @@ -739,27 +977,34 @@ var file_invoicesrpc_invoices_proto_goTypes = []interface{}{ (*SettleInvoiceResp)(nil), // 6: invoicesrpc.SettleInvoiceResp (*SubscribeSingleInvoiceRequest)(nil), // 7: invoicesrpc.SubscribeSingleInvoiceRequest (*LookupInvoiceMsg)(nil), // 8: invoicesrpc.LookupInvoiceMsg - (*lnrpc.RouteHint)(nil), // 9: lnrpc.RouteHint - (*lnrpc.Invoice)(nil), // 10: lnrpc.Invoice + (*CircuitKey)(nil), // 9: invoicesrpc.CircuitKey + (*InvoiceAcceptorRequest)(nil), // 10: invoicesrpc.InvoiceAcceptorRequest + (*InvoiceAcceptorResponse)(nil), // 11: invoicesrpc.InvoiceAcceptorResponse + (*lnrpc.RouteHint)(nil), // 12: lnrpc.RouteHint + (*lnrpc.Invoice)(nil), // 13: lnrpc.Invoice } var file_invoicesrpc_invoices_proto_depIdxs = []int32{ - 9, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint + 12, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint 0, // 1: invoicesrpc.LookupInvoiceMsg.lookup_modifier:type_name -> invoicesrpc.LookupModifier - 7, // 2: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest - 1, // 3: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg - 3, // 4: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest - 5, // 5: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg - 8, // 6: invoicesrpc.Invoices.LookupInvoiceV2:input_type -> invoicesrpc.LookupInvoiceMsg - 10, // 7: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice - 2, // 8: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp - 4, // 9: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp - 6, // 10: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp - 10, // 11: invoicesrpc.Invoices.LookupInvoiceV2:output_type -> lnrpc.Invoice - 7, // [7:12] is the sub-list for method output_type - 2, // [2:7] 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 + 13, // 2: invoicesrpc.InvoiceAcceptorRequest.invoice:type_name -> lnrpc.Invoice + 9, // 3: invoicesrpc.InvoiceAcceptorRequest.exit_htlc_circuit_key:type_name -> invoicesrpc.CircuitKey + 7, // 4: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest + 1, // 5: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg + 3, // 6: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest + 5, // 7: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg + 8, // 8: invoicesrpc.Invoices.LookupInvoiceV2:input_type -> invoicesrpc.LookupInvoiceMsg + 11, // 9: invoicesrpc.Invoices.InvoiceAcceptor:input_type -> invoicesrpc.InvoiceAcceptorResponse + 13, // 10: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice + 2, // 11: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp + 4, // 12: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp + 6, // 13: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp + 13, // 14: invoicesrpc.Invoices.LookupInvoiceV2:output_type -> lnrpc.Invoice + 10, // 15: invoicesrpc.Invoices.InvoiceAcceptor:output_type -> invoicesrpc.InvoiceAcceptorRequest + 10, // [10:16] is the sub-list for method output_type + 4, // [4:10] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_invoicesrpc_invoices_proto_init() } @@ -864,6 +1109,42 @@ func file_invoicesrpc_invoices_proto_init() { return nil } } + file_invoicesrpc_invoices_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CircuitKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InvoiceAcceptorRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_invoicesrpc_invoices_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InvoiceAcceptorResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_invoicesrpc_invoices_proto_msgTypes[7].OneofWrappers = []interface{}{ (*LookupInvoiceMsg_PaymentHash)(nil), @@ -876,7 +1157,7 @@ func file_invoicesrpc_invoices_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_invoicesrpc_invoices_proto_rawDesc, NumEnums: 1, - NumMessages: 8, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/invoicesrpc/invoices.proto b/lnrpc/invoicesrpc/invoices.proto index 7afffba4e..251d0d094 100644 --- a/lnrpc/invoicesrpc/invoices.proto +++ b/lnrpc/invoicesrpc/invoices.proto @@ -59,6 +59,14 @@ service Invoices { using either its payment hash, payment address, or set ID. */ rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice); + + /* + InvoiceAcceptor is a bi-directional streaming RPC that allows a client to + accept invoices. The server will send invoices to the client and the client + can respond with whether it accepts the invoice or not. + */ + rpc InvoiceAcceptor (stream InvoiceAcceptorResponse) + returns (stream InvoiceAcceptorRequest); } message CancelInvoiceMsg { @@ -192,3 +200,44 @@ message LookupInvoiceMsg { LookupModifier lookup_modifier = 4; } + +// CircuitKey is a unique identifier for an HTLC. +message CircuitKey { + /// The id of the channel that the is part of this circuit. + uint64 chan_id = 1; + + /// The index of the incoming htlc in the incoming channel. + uint64 htlc_id = 2; +} + +// InvoiceAcceptorRequest is a message that the server sends to the client to +// request the client to accept an invoice. +message InvoiceAcceptorRequest { + // invoice is the invoice that the client should consider accepting. + lnrpc.Invoice invoice = 1; + + // exit_htlc_circuit_key is the key of the HTLC for which we is involved in + // the invoice settlement attempt. + CircuitKey exit_htlc_circuit_key = 2; + + // exit_htlc_amt is the amount (millisats) that the client should consider + // accepting. + uint64 exit_htlc_amt = 3; + + // exit_htlc_expiry is the expiry time of the exit HTLC. + uint32 exit_htlc_expiry = 4; + + // current_height is the current block height. + uint32 current_height = 5; +} + +// InvoiceAcceptorResponse is a message that the client sends to the server to +// indicate whether it accepts the invoice or not. +message InvoiceAcceptorResponse { + // preimage is the preimage of the invoice. + bytes preimage = 1; + + // skip_amount_check is a flag that indicates whether the client wants to + // skip the amount check during the invoice settlement process. + bool skip_amount_check = 2; +} diff --git a/lnrpc/invoicesrpc/invoices.swagger.json b/lnrpc/invoicesrpc/invoices.swagger.json index b54d9fb38..cfefaff43 100644 --- a/lnrpc/invoicesrpc/invoices.swagger.json +++ b/lnrpc/invoicesrpc/invoices.swagger.json @@ -317,6 +317,51 @@ "invoicesrpcCancelInvoiceResp": { "type": "object" }, + "invoicesrpcCircuitKey": { + "type": "object", + "properties": { + "chan_id": { + "type": "string", + "format": "uint64", + "description": "/ The id of the channel that the is part of this circuit." + }, + "htlc_id": { + "type": "string", + "format": "uint64", + "description": "/ The index of the incoming htlc in the incoming channel." + } + }, + "description": "CircuitKey is a unique identifier for an HTLC." + }, + "invoicesrpcInvoiceAcceptorRequest": { + "type": "object", + "properties": { + "invoice": { + "$ref": "#/definitions/lnrpcInvoice", + "description": "invoice is the invoice that the client should consider accepting." + }, + "exit_htlc_circuit_key": { + "$ref": "#/definitions/invoicesrpcCircuitKey", + "description": "exit_htlc_circuit_key is the key of the HTLC for which we is involved in\nthe invoice settlement attempt." + }, + "exit_htlc_amt": { + "type": "string", + "format": "uint64", + "description": "exit_htlc_amt is the amount (millisats) that the client should consider\naccepting." + }, + "exit_htlc_expiry": { + "type": "integer", + "format": "int64", + "description": "exit_htlc_expiry is the expiry time of the exit HTLC." + }, + "current_height": { + "type": "integer", + "format": "int64", + "description": "current_height is the current block height." + } + }, + "description": "InvoiceAcceptorRequest is a message that the server sends to the client to\nrequest the client to accept an invoice." + }, "invoicesrpcLookupModifier": { "type": "string", "enum": [ diff --git a/lnrpc/invoicesrpc/invoices_grpc.pb.go b/lnrpc/invoicesrpc/invoices_grpc.pb.go index bc2f24ac1..398a2479e 100644 --- a/lnrpc/invoicesrpc/invoices_grpc.pb.go +++ b/lnrpc/invoicesrpc/invoices_grpc.pb.go @@ -39,6 +39,10 @@ type InvoicesClient interface { // LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced // using either its payment hash, payment address, or set ID. LookupInvoiceV2(ctx context.Context, in *LookupInvoiceMsg, opts ...grpc.CallOption) (*lnrpc.Invoice, error) + // InvoiceAcceptor is a bi-directional streaming RPC that allows a client to + // accept invoices. The server will send invoices to the client and the client + // can respond with whether it accepts the invoice or not. + InvoiceAcceptor(ctx context.Context, opts ...grpc.CallOption) (Invoices_InvoiceAcceptorClient, error) } type invoicesClient struct { @@ -117,6 +121,37 @@ func (c *invoicesClient) LookupInvoiceV2(ctx context.Context, in *LookupInvoiceM return out, nil } +func (c *invoicesClient) InvoiceAcceptor(ctx context.Context, opts ...grpc.CallOption) (Invoices_InvoiceAcceptorClient, error) { + stream, err := c.cc.NewStream(ctx, &Invoices_ServiceDesc.Streams[1], "/invoicesrpc.Invoices/InvoiceAcceptor", opts...) + if err != nil { + return nil, err + } + x := &invoicesInvoiceAcceptorClient{stream} + return x, nil +} + +type Invoices_InvoiceAcceptorClient interface { + Send(*InvoiceAcceptorResponse) error + Recv() (*InvoiceAcceptorRequest, error) + grpc.ClientStream +} + +type invoicesInvoiceAcceptorClient struct { + grpc.ClientStream +} + +func (x *invoicesInvoiceAcceptorClient) Send(m *InvoiceAcceptorResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *invoicesInvoiceAcceptorClient) Recv() (*InvoiceAcceptorRequest, error) { + m := new(InvoiceAcceptorRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // InvoicesServer is the server API for Invoices service. // All implementations must embed UnimplementedInvoicesServer // for forward compatibility @@ -141,6 +176,10 @@ type InvoicesServer interface { // LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced // using either its payment hash, payment address, or set ID. LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) + // InvoiceAcceptor is a bi-directional streaming RPC that allows a client to + // accept invoices. The server will send invoices to the client and the client + // can respond with whether it accepts the invoice or not. + InvoiceAcceptor(Invoices_InvoiceAcceptorServer) error mustEmbedUnimplementedInvoicesServer() } @@ -163,6 +202,9 @@ func (UnimplementedInvoicesServer) SettleInvoice(context.Context, *SettleInvoice func (UnimplementedInvoicesServer) LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) { return nil, status.Errorf(codes.Unimplemented, "method LookupInvoiceV2 not implemented") } +func (UnimplementedInvoicesServer) InvoiceAcceptor(Invoices_InvoiceAcceptorServer) error { + return status.Errorf(codes.Unimplemented, "method InvoiceAcceptor not implemented") +} func (UnimplementedInvoicesServer) mustEmbedUnimplementedInvoicesServer() {} // UnsafeInvoicesServer may be embedded to opt out of forward compatibility for this service. @@ -269,6 +311,32 @@ func _Invoices_LookupInvoiceV2_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Invoices_InvoiceAcceptor_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(InvoicesServer).InvoiceAcceptor(&invoicesInvoiceAcceptorServer{stream}) +} + +type Invoices_InvoiceAcceptorServer interface { + Send(*InvoiceAcceptorRequest) error + Recv() (*InvoiceAcceptorResponse, error) + grpc.ServerStream +} + +type invoicesInvoiceAcceptorServer struct { + grpc.ServerStream +} + +func (x *invoicesInvoiceAcceptorServer) Send(m *InvoiceAcceptorRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *invoicesInvoiceAcceptorServer) Recv() (*InvoiceAcceptorResponse, error) { + m := new(InvoiceAcceptorResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // Invoices_ServiceDesc is the grpc.ServiceDesc for Invoices service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -299,6 +367,12 @@ var Invoices_ServiceDesc = grpc.ServiceDesc{ Handler: _Invoices_SubscribeSingleInvoice_Handler, ServerStreams: true, }, + { + StreamName: "InvoiceAcceptor", + Handler: _Invoices_InvoiceAcceptor_Handler, + ServerStreams: true, + ClientStreams: true, + }, }, Metadata: "invoicesrpc/invoices.proto", } diff --git a/lnrpc/invoicesrpc/invoices_server.go b/lnrpc/invoicesrpc/invoices_server.go index b62538543..7ec039300 100644 --- a/lnrpc/invoicesrpc/invoices_server.go +++ b/lnrpc/invoicesrpc/invoices_server.go @@ -10,6 +10,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sync/atomic" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/invoices" @@ -31,6 +32,12 @@ const ( ) var ( + // ErrInvoiceAcceptorAlreadyExists signals that an invoice acceptor + // already exists. The user must disconnect an existing invoice accptor + // prior to opening another stream. + ErrInvoiceAcceptorAlreadyExists = errors.New("interceptor already " + + "exists") + // macaroonOps are the set of capabilities that our minted macaroon (if // it doesn't already exist) will have. macaroonOps = []bakery.Op{ @@ -66,6 +73,10 @@ var ( Entity: "invoices", Action: "write", }}, + "/invoicesrpc.Invoices/InvoiceAcceptor": {{ + Entity: "invoices", + Action: "write", + }}, } // DefaultInvoicesMacFilename is the default name of the invoices @@ -88,6 +99,12 @@ type Server struct { // Required by the grpc-gateway/v2 library for forward compatibility. UnimplementedInvoicesServer + // invoiceAcceptorActive is an atomic flag that indicates whether an + // invoice acceptor RPC server is currently active. It is used to ensure + // that only one invoice acceptor RPC server instance is running at a + // time. + invoiceAcceptorActive atomic.Int32 + quit chan struct{} cfg *Config @@ -447,3 +464,22 @@ func (s *Server) LookupInvoiceV2(ctx context.Context, return CreateRPCInvoice(&invoice, s.cfg.ChainParams) } + +// InvoiceAcceptor is a bidirectional streaming RPC that allows a client to +// inspect and optionally accept invoices. +func (s *Server) InvoiceAcceptor( + acceptorServer Invoices_InvoiceAcceptorServer) error { + + // Ensure that there is only one invoice acceptor RPC server instance. + if !s.invoiceAcceptorActive.CompareAndSwap(0, 1) { + return ErrInvoiceAcceptorAlreadyExists + } + defer s.invoiceAcceptorActive.CompareAndSwap(1, 0) + + // Run the invoice acceptor. + return newInvoiceAcceptor(invoiceAcceptorConfig{ + chainParams: s.cfg.ChainParams, + interceptor: s.cfg.InvoiceSettlementInterceptor, + rpcServer: acceptorServer, + }).run() +}