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") {
|
||||
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)
|
||||
@ -1169,8 +1176,9 @@ var addInvoiceCommand = cli.Command{
|
||||
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 "+
|
||||
|
48
rpcserver.go
48
rpcserver.go
@ -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.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
|
||||
}
|
||||
|
||||
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.
|
||||
// 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) {
|
||||
|
Loading…
Reference in New Issue
Block a user