Merge pull request #654 from wilmerpaulino/invoice-without-amount
Support invoices without amount specified
This commit is contained in:
commit
7919c4cba0
@ -1014,6 +1014,7 @@ func sendPayment(ctx *cli.Context) error {
|
|||||||
if ctx.IsSet("pay_req") {
|
if ctx.IsSet("pay_req") {
|
||||||
req = &lnrpc.SendRequest{
|
req = &lnrpc.SendRequest{
|
||||||
PaymentRequest: ctx.String("pay_req"),
|
PaymentRequest: ctx.String("pay_req"),
|
||||||
|
Amt: ctx.Int64("amt"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
@ -1138,6 +1139,11 @@ var payInvoiceCommand = cli.Command{
|
|||||||
Name: "pay_req",
|
Name: "pay_req",
|
||||||
Usage: "a zpay32 encoded payment request to fulfill",
|
Usage: "a zpay32 encoded payment request to fulfill",
|
||||||
},
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "amt",
|
||||||
|
Usage: "(optional) number of satoshis to fulfill the " +
|
||||||
|
"invoice",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: actionDecorator(payInvoice),
|
Action: actionDecorator(payInvoice),
|
||||||
}
|
}
|
||||||
@ -1158,6 +1164,7 @@ func payInvoice(ctx *cli.Context) error {
|
|||||||
|
|
||||||
req := &lnrpc.SendRequest{
|
req := &lnrpc.SendRequest{
|
||||||
PaymentRequest: payReq,
|
PaymentRequest: payReq,
|
||||||
|
Amt: ctx.Int64("amt"),
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendPaymentRequest(ctx, req)
|
return sendPaymentRequest(ctx, req)
|
||||||
@ -1168,9 +1175,10 @@ var addInvoiceCommand = cli.Command{
|
|||||||
Usage: "add a new invoice.",
|
Usage: "add a new invoice.",
|
||||||
Description: `
|
Description: `
|
||||||
Add a new invoice, expressing intent for a future payment.
|
Add a new invoice, expressing intent for a future payment.
|
||||||
|
|
||||||
The number of satoshis in this invoice is necessary for the creation,
|
Invoices without an amount can be created by not supplying any
|
||||||
the remaining parameters are optional.`,
|
parameters or providing an amount of 0. These invoices allow the payee
|
||||||
|
to specify the amount of satoshis they wish to send.`,
|
||||||
ArgsUsage: "value preimage",
|
ArgsUsage: "value preimage",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
@ -1239,8 +1247,6 @@ func addInvoice(ctx *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to decode amt argument: %v", err)
|
return fmt.Errorf("unable to decode amt argument: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return fmt.Errorf("amt argument missing")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -1469,7 +1469,15 @@ func (l *channelLink) processLockedInHtlcs(
|
|||||||
// Otherwise, we settle this htlc within our
|
// Otherwise, we settle this htlc within our
|
||||||
// local state update log, then send the update
|
// local state update log, then send the update
|
||||||
// entry to the remote party.
|
// entry to the remote party.
|
||||||
if !l.cfg.DebugHTLC && pd.Amount < invoice.Terms.Value {
|
//
|
||||||
|
// NOTE: We make an exception when the value
|
||||||
|
// requested by the invoice is zero. This means
|
||||||
|
// the invoice allows the payee to specify the
|
||||||
|
// amount of satoshis they wish to send.
|
||||||
|
// So since we expect the htlc to have a
|
||||||
|
// different amount, we should not fail.
|
||||||
|
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
|
||||||
|
pd.Amount < invoice.Terms.Value {
|
||||||
log.Errorf("rejecting htlc due to incorrect "+
|
log.Errorf("rejecting htlc due to incorrect "+
|
||||||
"amount: expected %v, received %v",
|
"amount: expected %v, received %v",
|
||||||
invoice.Terms.Value, pd.Amount)
|
invoice.Terms.Value, pd.Amount)
|
||||||
@ -1484,7 +1492,14 @@ func (l *channelLink) processLockedInHtlcs(
|
|||||||
// ensure that it was crafted correctly by the
|
// ensure that it was crafted correctly by the
|
||||||
// sender and matches the HTLC we were
|
// sender and matches the HTLC we were
|
||||||
// extended.
|
// extended.
|
||||||
if !l.cfg.DebugHTLC &&
|
//
|
||||||
|
// NOTE: We make an exception when the value
|
||||||
|
// requested by the invoice is zero. This means
|
||||||
|
// the invoice allows the payee to specify the
|
||||||
|
// amount of satoshis they wish to send.
|
||||||
|
// So since we expect the htlc to have a
|
||||||
|
// different amount, we should not fail.
|
||||||
|
if !l.cfg.DebugHTLC && invoice.Terms.Value > 0 &&
|
||||||
fwdInfo.AmountToForward != invoice.Terms.Value {
|
fwdInfo.AmountToForward != invoice.Terms.Value {
|
||||||
|
|
||||||
log.Errorf("Onion payload of incoming "+
|
log.Errorf("Onion payload of incoming "+
|
||||||
|
50
rpcserver.go
50
rpcserver.go
@ -1623,18 +1623,20 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
|
|||||||
}
|
}
|
||||||
p.dest = payReq.Destination.SerializeCompressed()
|
p.dest = payReq.Destination.SerializeCompressed()
|
||||||
|
|
||||||
|
// If the amount was not included in the
|
||||||
|
// invoice, then we let the payee
|
||||||
|
// specify the amount of satoshis they
|
||||||
|
// wish to send. We override the amount
|
||||||
|
// to pay with the amount provided from
|
||||||
|
// the payment request.
|
||||||
if payReq.MilliSat == nil {
|
if payReq.MilliSat == nil {
|
||||||
err := fmt.Errorf("only payment" +
|
p.msat = lnwire.NewMSatFromSatoshis(
|
||||||
" requests specifying" +
|
btcutil.Amount(nextPayment.Amt),
|
||||||
" the amount are" +
|
)
|
||||||
" currently supported")
|
} else {
|
||||||
select {
|
p.msat = *payReq.MilliSat
|
||||||
case errChan <- err:
|
|
||||||
case <-reqQuit:
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
p.msat = *payReq.MilliSat
|
|
||||||
p.pHash = payReq.PaymentHash[:]
|
p.pHash = payReq.PaymentHash[:]
|
||||||
p.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
p.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
||||||
} else {
|
} else {
|
||||||
@ -1806,11 +1808,18 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
|
|||||||
|
|
||||||
destPub = payReq.Destination
|
destPub = payReq.Destination
|
||||||
|
|
||||||
|
// If the amount was not included in the invoice, then we let
|
||||||
|
// the payee specify the amount of satoshis they wish to send.
|
||||||
|
// We override the amount to pay with the amount provided from
|
||||||
|
// the payment request.
|
||||||
if payReq.MilliSat == nil {
|
if payReq.MilliSat == nil {
|
||||||
return nil, fmt.Errorf("payment requests with no " +
|
amtMSat = lnwire.NewMSatFromSatoshis(
|
||||||
"amount specified not currently supported")
|
btcutil.Amount(nextPayment.Amt),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
amtMSat = *payReq.MilliSat
|
||||||
}
|
}
|
||||||
amtMSat = *payReq.MilliSat
|
|
||||||
rHash = *payReq.PaymentHash
|
rHash = *payReq.PaymentHash
|
||||||
cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
|
||||||
|
|
||||||
@ -1939,14 +1948,10 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
|||||||
|
|
||||||
amt := btcutil.Amount(invoice.Value)
|
amt := btcutil.Amount(invoice.Value)
|
||||||
amtMSat := lnwire.NewMSatFromSatoshis(amt)
|
amtMSat := lnwire.NewMSatFromSatoshis(amt)
|
||||||
switch {
|
|
||||||
// The value of an invoice MUST NOT be zero.
|
|
||||||
case invoice.Value == 0:
|
|
||||||
return nil, fmt.Errorf("zero value invoices are disallowed")
|
|
||||||
|
|
||||||
// The value of the invoice must also not exceed the current soft-limit
|
// The value of the invoice must also not exceed the current soft-limit
|
||||||
// on the largest payment within the network.
|
// on the largest payment within the network.
|
||||||
case amtMSat > maxPaymentMSat:
|
if amtMSat > maxPaymentMSat {
|
||||||
return nil, fmt.Errorf("payment of %v is too large, max "+
|
return nil, fmt.Errorf("payment of %v is too large, max "+
|
||||||
"payment allowed is %v", amt, maxPaymentMSat.ToSatoshis())
|
"payment allowed is %v", amt, maxPaymentMSat.ToSatoshis())
|
||||||
}
|
}
|
||||||
@ -1962,9 +1967,12 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
|||||||
// expiry, fallback address, and the amount field.
|
// expiry, fallback address, and the amount field.
|
||||||
var options []func(*zpay32.Invoice)
|
var options []func(*zpay32.Invoice)
|
||||||
|
|
||||||
// Add the amount. This field is optional by the BOLT-11 format, but
|
// We only include the amount in the invoice if it is greater than 0.
|
||||||
// we require it for now.
|
// By not including the amount, we enable the creation of invoices that
|
||||||
options = append(options, zpay32.Amount(amtMSat))
|
// allow the payee to specify the amount of satoshis they wish to send.
|
||||||
|
if amtMSat > 0 {
|
||||||
|
options = append(options, zpay32.Amount(amtMSat))
|
||||||
|
}
|
||||||
|
|
||||||
// If specified, add a fallback address to the payment request.
|
// If specified, add a fallback address to the payment request.
|
||||||
if len(invoice.FallbackAddr) > 0 {
|
if len(invoice.FallbackAddr) > 0 {
|
||||||
|
@ -233,6 +233,26 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode.
|
skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode.
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Invoice with no amount.
|
||||||
|
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jshwlglv23cytkzvq8ld39drs8sq656yh2zn0aevrwu6uqctaklelhtpjnmgjdzmvwsh0kuxuwqf69fjeap9m5mev2qzpp27xfswhs5vgqmn9xzq",
|
||||||
|
valid: true,
|
||||||
|
decodedInvoice: func() *Invoice {
|
||||||
|
return &Invoice{
|
||||||
|
Net: &chaincfg.MainNetParams,
|
||||||
|
Timestamp: time.Unix(1496314658, 0),
|
||||||
|
PaymentHash: &testPaymentHash,
|
||||||
|
Description: &testCupOfCoffee,
|
||||||
|
Destination: testPubKey,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeEncoding: func(i *Invoice) {
|
||||||
|
// Since this destination pubkey was recovered
|
||||||
|
// from the signature, we must set it nil before
|
||||||
|
// encoding to get back the same invoice string.
|
||||||
|
i.Destination = nil
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Please make a donation of any amount using rhash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad
|
// Please make a donation of any amount using rhash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad
|
||||||
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w",
|
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w",
|
||||||
@ -531,6 +551,19 @@ func TestNewInvoice(t *testing.T) {
|
|||||||
},
|
},
|
||||||
valid: false, // Both Description and DescriptionHash set.
|
valid: false, // Both Description and DescriptionHash set.
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Invoice with no amount.
|
||||||
|
newInvoice: func() (*Invoice, error) {
|
||||||
|
return NewInvoice(
|
||||||
|
&chaincfg.MainNetParams,
|
||||||
|
testPaymentHash,
|
||||||
|
time.Unix(1496314658, 0),
|
||||||
|
Description(testCupOfCoffee),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jshwlglv23cytkzvq8ld39drs8sq656yh2zn0aevrwu6uqctaklelhtpjnmgjdzmvwsh0kuxuwqf69fjeap9m5mev2qzpp27xfswhs5vgqmn9xzq",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 'n' field set.
|
// 'n' field set.
|
||||||
newInvoice: func() (*Invoice, error) {
|
newInvoice: func() (*Invoice, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user