Merge pull request #654 from wilmerpaulino/invoice-without-amount

Support invoices without amount specified
This commit is contained in:
Olaoluwa Osuntokun 2018-01-27 17:38:56 -08:00 committed by GitHub
commit 7919c4cba0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 28 deletions

@ -1014,6 +1014,7 @@ func sendPayment(ctx *cli.Context) error {
if ctx.IsSet("pay_req") {
req = &lnrpc.SendRequest{
PaymentRequest: ctx.String("pay_req"),
Amt: ctx.Int64("amt"),
}
} else {
args := ctx.Args()
@ -1138,6 +1139,11 @@ var payInvoiceCommand = cli.Command{
Name: "pay_req",
Usage: "a zpay32 encoded payment request to fulfill",
},
cli.Int64Flag{
Name: "amt",
Usage: "(optional) number of satoshis to fulfill the " +
"invoice",
},
},
Action: actionDecorator(payInvoice),
}
@ -1158,6 +1164,7 @@ func payInvoice(ctx *cli.Context) error {
req := &lnrpc.SendRequest{
PaymentRequest: payReq,
Amt: ctx.Int64("amt"),
}
return sendPaymentRequest(ctx, req)
@ -1168,9 +1175,10 @@ var addInvoiceCommand = cli.Command{
Usage: "add a new invoice.",
Description: `
Add a new invoice, expressing intent for a future payment.
The number of satoshis in this invoice is necessary for the creation,
the remaining parameters are optional.`,
Invoices without an amount can be created by not supplying any
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",
Flags: []cli.Flag{
cli.StringFlag{
@ -1239,8 +1247,6 @@ func addInvoice(ctx *cli.Context) error {
if err != nil {
return fmt.Errorf("unable to decode amt argument: %v", err)
}
default:
return fmt.Errorf("amt argument missing")
}
switch {

@ -1469,7 +1469,15 @@ func (l *channelLink) processLockedInHtlcs(
// Otherwise, we settle this htlc within our
// local state update log, then send the update
// 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 "+
"amount: expected %v, received %v",
invoice.Terms.Value, pd.Amount)
@ -1484,7 +1492,14 @@ func (l *channelLink) processLockedInHtlcs(
// ensure that it was crafted correctly by the
// sender and matches the HTLC we were
// 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 {
log.Errorf("Onion payload of incoming "+

@ -1623,18 +1623,20 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
}
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 {
err := fmt.Errorf("only payment" +
" requests specifying" +
" the amount are" +
" currently supported")
select {
case errChan <- err:
case <-reqQuit:
}
return
p.msat = lnwire.NewMSatFromSatoshis(
btcutil.Amount(nextPayment.Amt),
)
} else {
p.msat = *payReq.MilliSat
}
p.msat = *payReq.MilliSat
p.pHash = payReq.PaymentHash[:]
p.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
} else {
@ -1806,11 +1808,18 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
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 {
return nil, fmt.Errorf("payment requests with no " +
"amount specified not currently supported")
amtMSat = lnwire.NewMSatFromSatoshis(
btcutil.Amount(nextPayment.Amt),
)
} else {
amtMSat = *payReq.MilliSat
}
amtMSat = *payReq.MilliSat
rHash = *payReq.PaymentHash
cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
@ -1939,14 +1948,10 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
amt := btcutil.Amount(invoice.Value)
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
// on the largest payment within the network.
case amtMSat > maxPaymentMSat:
if amtMSat > maxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max "+
"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.
var options []func(*zpay32.Invoice)
// Add the amount. This field is optional by the BOLT-11 format, but
// we require it for now.
options = append(options, zpay32.Amount(amtMSat))
// We only include the amount in the invoice if it is greater than 0.
// By not including the amount, we enable the creation of invoices that
// 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 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.
},
{
// 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
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w",
@ -531,6 +551,19 @@ func TestNewInvoice(t *testing.T) {
},
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.
newInvoice: func() (*Invoice, error) {