Merge pull request #5253 from cfromknecht/amp-invoice

Support paying AMP invoices via SendPaymentV2
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2021-05-12 13:38:42 -07:00
commit dc73a23e81
30 files changed
+1528 -1023

No files matched your search

+92 -15
View File
@@ -54,6 +54,10 @@ type AddInvoiceConfig struct {
// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
// GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
}
// AddInvoiceData contains the required data to create a new invoice.
@@ -99,17 +103,71 @@ type AddInvoiceData struct {
// immediately upon receiving the payment.
HodlInvoice bool
// Amp signals whether or not to create an AMP invoice.
//
// NOTE: Preimage should always be set to nil when this value is true.
Amp bool
// RouteHints are optional route hints that can each be individually used
// to assist in reaching the invoice's destination.
RouteHints [][]zpay32.HopHint
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) {
// paymentHashAndPreimage returns the payment hash and preimage for this invoice
// depending on the configuration.
//
// For AMP invoices (when Amp flag is true), this method always returns a nil
// preimage. The hash value can be set externally by the user using the Hash
// field, or one will be generated randomly. The payment hash here only serves
// as a unique identifier for insertion into the invoice index, as there is
// no universal preimage for an AMP payment.
//
// For MPP invoices (when Amp flag is false), this method may return nil
// preimage when create a hodl invoice, but otherwise will always return a
// non-nil preimage and the corresponding payment hash. The valid combinations
// are parsed as follows:
// - Preimage == nil && Hash == nil -> (random preimage, H(random preimage))
// - Preimage != nil && Hash == nil -> (Preimage, H(Preimage))
// - Preimage == nil && Hash != nil -> (nil, Hash)
func (d *AddInvoiceData) paymentHashAndPreimage() (
*lntypes.Preimage, lntypes.Hash, error) {
if d.Amp {
return d.ampPaymentHashAndPreimage()
}
return d.mppPaymentHashAndPreimage()
}
// ampPaymentHashAndPreimage returns the payment hash to use for an AMP invoice.
// The preimage will always be nil.
func (d *AddInvoiceData) ampPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
switch {
// Preimages cannot be set on AMP invoice.
case d.Preimage != nil:
return nil, lntypes.Hash{},
errors.New("preimage set on AMP invoice")
// If a specific hash was requested, use that.
case d.Hash != nil:
return nil, *d.Hash, nil
// Otherwise generate a random hash value, just needs to be unique to be
// added to the invoice index.
default:
var paymentHash lntypes.Hash
if _, err := rand.Read(paymentHash[:]); err != nil {
return nil, lntypes.Hash{}, err
}
return nil, paymentHash, nil
}
}
// mppPaymentHashAndPreimage returns the payment hash and preimage to use for an
// MPP invoice.
func (d *AddInvoiceData) mppPaymentHashAndPreimage() (*lntypes.Preimage, lntypes.Hash, error) {
var (
paymentPreimage *lntypes.Preimage
paymentHash lntypes.Hash
@@ -118,28 +176,42 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
switch {
// Only either preimage or hash can be set.
case invoice.Preimage != nil && invoice.Hash != nil:
return nil, nil,
case d.Preimage != nil && d.Hash != nil:
return nil, lntypes.Hash{},
errors.New("preimage and hash both set")
// If no hash or preimage is given, generate a random preimage.
case invoice.Preimage == nil && invoice.Hash == nil:
case d.Preimage == nil && d.Hash == nil:
paymentPreimage = &lntypes.Preimage{}
if _, err := rand.Read(paymentPreimage[:]); err != nil {
return nil, nil, err
return nil, lntypes.Hash{}, err
}
paymentHash = paymentPreimage.Hash()
// If just a hash is given, we create a hold invoice by setting the
// preimage to unknown.
case invoice.Preimage == nil && invoice.Hash != nil:
paymentHash = *invoice.Hash
case d.Preimage == nil && d.Hash != nil:
paymentHash = *d.Hash
// A specific preimage was supplied. Use that for the invoice.
case invoice.Preimage != nil && invoice.Hash == nil:
preimage := *invoice.Preimage
case d.Preimage != nil && d.Hash == nil:
preimage := *d.Preimage
paymentPreimage = &preimage
paymentHash = invoice.Preimage.Hash()
paymentHash = d.Preimage.Hash()
}
return paymentPreimage, paymentHash, nil
}
// AddInvoice attempts to add a new invoice to the invoice database. Any
// duplicated invoices are rejected, therefore all invoices *must* have a
// unique payment preimage.
func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) {
paymentPreimage, paymentHash, err := invoice.paymentHashAndPreimage()
if err != nil {
return nil, nil, err
}
// The size of the memo, receipt and description hash attached must not
@@ -307,7 +379,12 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
}
// Set our desired invoice features and add them to our list of options.
invoiceFeatures := cfg.GenInvoiceFeatures()
var invoiceFeatures *lnwire.FeatureVector
if invoice.Amp {
invoiceFeatures = cfg.GenAmpInvoiceFeatures()
} else {
invoiceFeatures = cfg.GenInvoiceFeatures()
}
options = append(options, zpay32.Features(invoiceFeatures))
// Generate and set a random payment address for this invoice. If the
+4
View File
@@ -55,4 +55,8 @@ type Config struct {
// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
// GenAmpInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated AMP invoices.
GenAmpInvoiceFeatures func() *lnwire.FeatureVector
}
+5
View File
@@ -463,6 +463,11 @@
"type": "string",
"format": "byte",
"description": "The payment address of this invoice. This value will be used in MPP\npayments, and also for newer invoies that always require the MPP paylaod\nfor added end-to-end security."
},
"is_amp": {
"type": "boolean",
"format": "boolean",
"description": "Signals whether or not this is an AMP invoice."
}
}
},
+9 -8
View File
@@ -308,14 +308,15 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
invoice *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) {
addInvoiceCfg := &AddInvoiceConfig{
AddInvoice: s.cfg.InvoiceRegistry.AddInvoice,
IsChannelActive: s.cfg.IsChannelActive,
ChainParams: s.cfg.ChainParams,
NodeSigner: s.cfg.NodeSigner,
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
ChanDB: s.cfg.RemoteChanDB,
Graph: s.cfg.LocalChanDB.ChannelGraph(),
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
AddInvoice: s.cfg.InvoiceRegistry.AddInvoice,
IsChannelActive: s.cfg.IsChannelActive,
ChainParams: s.cfg.ChainParams,
NodeSigner: s.cfg.NodeSigner,
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
ChanDB: s.cfg.RemoteChanDB,
Graph: s.cfg.LocalChanDB.ChannelGraph(),
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures,
}
hash, err := lntypes.MakeHash(invoice.Hash)
+4 -1
View File
@@ -147,6 +147,8 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
rpcHtlcs = append(rpcHtlcs, &rpcHtlc)
}
isAmp := invoice.Terms.Features.HasFeature(lnwire.AMPOptional)
rpcInvoice := &lnrpc.Invoice{
Memo: string(invoice.Memo),
RHash: rHash,
@@ -170,8 +172,9 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
State: state,
Htlcs: rpcHtlcs,
Features: CreateRPCFeatures(invoice.Terms.Features),
IsKeysend: len(invoice.PaymentRequest) == 0,
IsKeysend: len(invoice.PaymentRequest) == 0 && !isAmp,
PaymentAddr: invoice.Terms.PaymentAddr[:],
IsAmp: isAmp,
}
if preimage != nil {
+28 -5
View File
@@ -696,13 +696,37 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.Amount = *payReq.MilliSat
}
if !payReq.Features.HasFeature(lnwire.MPPOptional) {
if !payReq.Features.HasFeature(lnwire.MPPOptional) &&
!payReq.Features.HasFeature(lnwire.AMPOptional) {
payIntent.MaxParts = 1
}
err = payIntent.SetPaymentHash(*payReq.PaymentHash)
if err != nil {
return nil, err
if payReq.Features.HasFeature(lnwire.AMPOptional) {
// Generate random SetID and root share.
var setID [32]byte
_, err = rand.Read(setID[:])
if err != nil {
return nil, err
}
var rootShare [32]byte
_, err = rand.Read(rootShare[:])
if err != nil {
return nil, err
}
err := payIntent.SetAMP(&routing.AMPOptions{
SetID: setID,
RootShare: rootShare,
})
if err != nil {
return nil, err
}
} else {
err = payIntent.SetPaymentHash(*payReq.PaymentHash)
if err != nil {
return nil, err
}
}
destKey := payReq.Destination.SerializeCompressed()
@@ -765,7 +789,6 @@ func (r *RouterBackend) extractIntentFromSendRequest(
ampFeatures := []lnrpc.FeatureBit{
lnrpc.FeatureBit_TLV_ONION_OPT,
lnrpc.FeatureBit_PAYMENT_ADDR_OPT,
lnrpc.FeatureBit_MPP_OPT,
lnrpc.FeatureBit_AMP_OPT,
}
+906 -895
View File
File diff suppressed because it is too large. Load diff
+5
View File
@@ -3003,6 +3003,11 @@ message Invoice {
for added end-to-end security.
*/
bytes payment_addr = 26;
/*
Signals whether or not this is an AMP invoice.
*/
bool is_amp = 27;
}
enum InvoiceHTLCState {
+5
View File
@@ -4387,6 +4387,11 @@
"type": "string",
"format": "byte",
"description": "The payment address of this invoice. This value will be used in MPP\npayments, and also for newer invoies that always require the MPP paylaod\nfor added end-to-end security."
},
"is_amp": {
"type": "boolean",
"format": "boolean",
"description": "Signals whether or not this is an AMP invoice."
}
}
},