mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-04-05 02:28:11 +02:00
Merge pull request #5803 from Roasbeef/amp-set-id-index
channeldb+invoices: enable repeated payments to AMP invoices via new HTLC key prefix storage
This commit is contained in:
commit
52146e0970
@ -1,6 +1,7 @@
|
||||
package channeldb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -49,7 +51,8 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
|
||||
Value: value,
|
||||
Features: emptyFeatures,
|
||||
},
|
||||
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||
AMPState: map[SetID]InvoiceStateAMP{},
|
||||
}
|
||||
i.Memo = []byte("memo")
|
||||
|
||||
@ -212,7 +215,7 @@ func testInvoiceWorkflow(t *testing.T, test invWorkflowTest) {
|
||||
// now have the settled bit toggle to true and a non-default
|
||||
// SettledDate
|
||||
payAmt := fakeInvoice.Terms.Value * 2
|
||||
_, err = db.UpdateInvoice(ref, getUpdateInvoice(payAmt))
|
||||
_, err = db.UpdateInvoice(ref, nil, getUpdateInvoice(payAmt))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle invoice: %v", err)
|
||||
}
|
||||
@ -429,7 +432,7 @@ func TestInvRefEquivocation(t *testing.T) {
|
||||
nop := func(_ *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return nil, nil
|
||||
}
|
||||
_, err = db.UpdateInvoice(ref, nop)
|
||||
_, err = db.UpdateInvoice(ref, nil, nop)
|
||||
require.Error(t, err, ErrInvRefEquivocation)
|
||||
}
|
||||
|
||||
@ -468,7 +471,7 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
|
||||
}
|
||||
|
||||
ref := InvoiceRefByHash(paymentHash)
|
||||
invoice, err := db.UpdateInvoice(ref,
|
||||
invoice, err := db.UpdateInvoice(ref, nil,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
@ -487,7 +490,7 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
|
||||
}
|
||||
|
||||
// Cancel the htlc again.
|
||||
invoice, err = db.UpdateInvoice(ref,
|
||||
invoice, err = db.UpdateInvoice(ref, nil,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: map[CircuitKey]struct{}{
|
||||
@ -506,6 +509,169 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvoiceCancelSingleHtlcAMP tests that it's possible to cancel a single
|
||||
// invoice of an AMP HTLC across multiple set IDs, and also have that update
|
||||
// the amount paid and other related fields as well.
|
||||
func TestInvoiceCancelSingleHtlcAMP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, cleanUp, err := MakeTestDB(OptionClock(testClock))
|
||||
defer cleanUp()
|
||||
require.NoError(t, err, "unable to make test db: %v", err)
|
||||
|
||||
// We'll start out by creating an invoice and writing it to the DB.
|
||||
amt := lnwire.NewMSatFromSatoshis(1000)
|
||||
invoice, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Set AMP-specific features so that we can settle with HTLC-level
|
||||
// preimages.
|
||||
invoice.Terms.Features = ampFeatures
|
||||
|
||||
preimage := *invoice.Terms.PaymentPreimage
|
||||
payHash := preimage.Hash()
|
||||
_, err = db.AddInvoice(invoice, payHash)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Add two HTLC sets, one with one HTLC and the other with two.
|
||||
setID1 := &[32]byte{1}
|
||||
setID2 := &[32]byte{2}
|
||||
|
||||
ref := InvoiceRefByHashAndAddr(payHash, invoice.Terms.PaymentAddr)
|
||||
|
||||
// The first set ID with a single HTLC added.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID1), updateAcceptAMPHtlc(0, amt, setID1, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// The second set ID with two HTLCs added.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2), updateAcceptAMPHtlc(1, amt, setID2, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
dbInvoice, err := db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2), updateAcceptAMPHtlc(2, amt, setID2, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// At this point, we should detect that 3k satoshis total has been
|
||||
// paid.
|
||||
require.Equal(t, dbInvoice.AmtPaid, amt*3)
|
||||
|
||||
// Now we'll cancel a single invoice, and assert that the amount paid
|
||||
// is decremented, and the state for that HTLC set reflects that is
|
||||
// been cancelled.
|
||||
_, err = db.UpdateInvoice(ref, (*SetID)(setID1),
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
SetID: (*SetID)(setID1),
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to cancel htlc: %v", err)
|
||||
}
|
||||
|
||||
freshInvoice, err := db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
// The amount paid should reflect that an invoice was cancelled.
|
||||
require.Equal(t, dbInvoice.AmtPaid, amt*2)
|
||||
|
||||
// The HTLC and AMP state should also show that only one HTLC set is
|
||||
// left.
|
||||
invoice.State = ContractOpen
|
||||
invoice.AmtPaid = 2 * amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
{HtlcID: 0}: makeAMPInvoiceHTLC(amt, *setID1, payHash, &preimage),
|
||||
{HtlcID: 1}: makeAMPInvoiceHTLC(amt, *setID2, payHash, &preimage),
|
||||
{HtlcID: 2}: makeAMPInvoiceHTLC(amt, *setID2, payHash, &preimage),
|
||||
}
|
||||
invoice.AMPState[*setID1] = InvoiceStateAMP{
|
||||
State: HtlcStateCanceled,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
}
|
||||
invoice.AMPState[*setID2] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt * 2,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 1}: {},
|
||||
{HtlcID: 2}: {},
|
||||
},
|
||||
}
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].ResolveTime = time.Unix(1, 0)
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// Next, we'll cancel the _other_ HTLCs active, but we'll do them one
|
||||
// by one.
|
||||
_, err = db.UpdateInvoice(ref, (*SetID)(setID2),
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: map[CircuitKey]struct{}{
|
||||
{HtlcID: 1}: {},
|
||||
},
|
||||
SetID: (*SetID)(setID2),
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to cancel htlc: %v", err)
|
||||
}
|
||||
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.AmtPaid = amt
|
||||
|
||||
ampState := invoice.AMPState[*setID2]
|
||||
ampState.State = HtlcStateCanceled
|
||||
ampState.AmtPaid = amt
|
||||
invoice.AMPState[*setID2] = ampState
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// Now we'll cancel the final HTLC, which should cause all the active
|
||||
// HTLCs to transition to the cancelled state.
|
||||
_, err = db.UpdateInvoice(ref, (*SetID)(setID2),
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: map[CircuitKey]struct{}{
|
||||
{HtlcID: 2}: {},
|
||||
},
|
||||
SetID: (*SetID)(setID2),
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unable to cancel htlc: %v", err)
|
||||
}
|
||||
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
ampState = invoice.AMPState[*setID2]
|
||||
ampState.AmtPaid = 0
|
||||
invoice.AMPState[*setID2] = ampState
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.AmtPaid = 0
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
}
|
||||
|
||||
// TestInvoiceTimeSeries tests that newly added invoices invoices, as well as
|
||||
// settled invoices are added to the database are properly placed in the add
|
||||
// add or settle index which serves as an event time series.
|
||||
@ -605,7 +771,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
|
||||
|
||||
ref := InvoiceRefByHash(paymentHash)
|
||||
_, err := db.UpdateInvoice(
|
||||
ref, getUpdateInvoice(invoice.Terms.Value),
|
||||
ref, nil, getUpdateInvoice(invoice.Terms.Value),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle invoice: %v", err)
|
||||
@ -662,7 +828,167 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestScanInvoices tests that ScanInvoices scans trough all stored invoices
|
||||
// TestSettleIndexAmpPayments tests that repeated settles of the same invoice
|
||||
// end up properly adding entries to the settle index, and the
|
||||
// InvoicesSettledSince will emit a "projected" version of the invoice w/
|
||||
// _just_ that HTLC information.
|
||||
func TestSettleIndexAmpPayments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testClock := clock.NewTestClock(testNow)
|
||||
db, cleanUp, err := MakeTestDB(OptionClock(testClock))
|
||||
defer cleanUp()
|
||||
require.Nil(t, err)
|
||||
|
||||
// First, we'll make a sample invoice that'll be paid to several times
|
||||
// below.
|
||||
amt := lnwire.NewMSatFromSatoshis(1000)
|
||||
testInvoice, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
testInvoice.Terms.Features = ampFeatures
|
||||
|
||||
// Add the invoice to the DB, we use a dummy payment hash here but the
|
||||
// invoice will have a valid payment address set.
|
||||
preimage := *testInvoice.Terms.PaymentPreimage
|
||||
payHash := preimage.Hash()
|
||||
_, err = db.AddInvoice(testInvoice, payHash)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Now that we have the invoice, we'll simulate 3 different HTLC sets
|
||||
// being attached to the invoice. These represent 3 different
|
||||
// concurrent payments.
|
||||
setID1 := &[32]byte{1}
|
||||
setID2 := &[32]byte{2}
|
||||
setID3 := &[32]byte{3}
|
||||
|
||||
ref := InvoiceRefByHashAndAddr(payHash, testInvoice.Terms.PaymentAddr)
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID1), updateAcceptAMPHtlc(1, amt, setID1, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2), updateAcceptAMPHtlc(2, amt, setID2, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID3), updateAcceptAMPHtlc(3, amt, setID3, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Now that the invoices have been accepted, we'll exercise the
|
||||
// behavior of the LookupInvoice call that allows us to modify exactly
|
||||
// how we query for invoices.
|
||||
//
|
||||
// First, we'll query for the invoice with just the payment addr, but
|
||||
// specify no HTLcs are to be included.
|
||||
refNoHtlcs := InvoiceRefByAddrBlankHtlc(testInvoice.Terms.PaymentAddr)
|
||||
invoiceNoHTLCs, err := db.LookupInvoice(refNoHtlcs)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, 0, len(invoiceNoHTLCs.Htlcs))
|
||||
|
||||
// We'll now look up the HTLCs based on the individual setIDs added
|
||||
// above.
|
||||
for i, setID := range []*[32]byte{setID1, setID2, setID3} {
|
||||
refFiltered := InvoiceRefBySetIDFiltered(*setID)
|
||||
invoiceFiltered, err := db.LookupInvoice(refFiltered)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Only a single HTLC should be present.
|
||||
require.Equal(t, 1, len(invoiceFiltered.Htlcs))
|
||||
|
||||
// The set ID for the HTLC should match the queried set ID.
|
||||
key := CircuitKey{HtlcID: uint64(i + 1)}
|
||||
htlc := invoiceFiltered.Htlcs[key]
|
||||
require.Equal(t, *setID, htlc.AMP.Record.SetID())
|
||||
|
||||
// The HTLC should show that it's in the accepted state.
|
||||
require.Equal(t, htlc.State, HtlcStateAccepted)
|
||||
}
|
||||
|
||||
// Now that we know the invoices are in the proper state, we'll settle
|
||||
// them on by one in distinct updates.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID1),
|
||||
getUpdateInvoiceAMPSettle(
|
||||
setID1, preimage, CircuitKey{HtlcID: 1},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2),
|
||||
getUpdateInvoiceAMPSettle(
|
||||
setID2, preimage, CircuitKey{HtlcID: 2},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID3),
|
||||
getUpdateInvoiceAMPSettle(
|
||||
setID3, preimage, CircuitKey{HtlcID: 3},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Now that all the invoices have been settled, we'll ensure that the
|
||||
// settle index was updated properly by obtaining all the currently
|
||||
// settled invoices in the time series. We use a value of 1 here to
|
||||
// ensure we get _all_ the invoices back.
|
||||
settledInvoices, err := db.InvoicesSettledSince(1)
|
||||
require.Nil(t, err)
|
||||
|
||||
// To get around the settle index quirk, we'll fetch the very first
|
||||
// invoice in the HTLC filtered mode and append it to the set of
|
||||
// invoices.
|
||||
firstInvoice, err := db.LookupInvoice(InvoiceRefBySetIDFiltered(*setID1))
|
||||
require.Nil(t, err)
|
||||
settledInvoices = append([]Invoice{firstInvoice}, settledInvoices...)
|
||||
|
||||
// There should be 3 invoices settled, as we created 3 "sub-invoices"
|
||||
// above.
|
||||
numInvoices := 3
|
||||
require.Equal(t, numInvoices, len(settledInvoices))
|
||||
|
||||
// Each invoice should match the set of invoices we settled above, and
|
||||
// the AMPState should be set accordingly.
|
||||
for i, settledInvoice := range settledInvoices {
|
||||
// Only one HTLC should be projected for this settled index.
|
||||
require.Equal(t, 1, len(settledInvoice.Htlcs))
|
||||
|
||||
// The invoice should show up as settled, and match the settle
|
||||
// index increment.
|
||||
invSetID := &[32]byte{byte(i + 1)}
|
||||
subInvoiceState, ok := settledInvoice.AMPState[*invSetID]
|
||||
require.True(t, ok)
|
||||
|
||||
require.Equal(t, subInvoiceState.State, HtlcStateSettled)
|
||||
require.Equal(t, int(subInvoiceState.SettleIndex), i+1)
|
||||
|
||||
invoiceKey := CircuitKey{HtlcID: uint64(i + 1)}
|
||||
_, keyFound := subInvoiceState.InvoiceKeys[invoiceKey]
|
||||
require.True(t, keyFound)
|
||||
}
|
||||
|
||||
// If we attempt to look up the invoice by the payment addr, with all
|
||||
// the HTLCs, the main invoice should have 3 HTLCs present.
|
||||
refWithHtlcs := InvoiceRefByAddr(testInvoice.Terms.PaymentAddr)
|
||||
invoiceWithHTLCs, err := db.LookupInvoice(refWithHtlcs)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, numInvoices, len(invoiceWithHTLCs.Htlcs))
|
||||
|
||||
// Finally, delete the invoice. If we query again, then nothing should
|
||||
// be found.
|
||||
err = db.DeleteInvoice([]InvoiceDeleteRef{
|
||||
{
|
||||
PayHash: payHash,
|
||||
PayAddr: &testInvoice.Terms.PaymentAddr,
|
||||
AddIndex: testInvoice.AddIndex,
|
||||
},
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
// TestScanInvoices tests that ScanInvoices scans through all stored invoices
|
||||
// correctly.
|
||||
func TestScanInvoices(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -750,7 +1076,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
||||
|
||||
// With the invoice in the DB, we'll now attempt to settle the invoice.
|
||||
ref := InvoiceRefByHash(payHash)
|
||||
dbInvoice, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
|
||||
dbInvoice, err := db.UpdateInvoice(ref, nil, getUpdateInvoice(amt))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle invoice: %v", err)
|
||||
}
|
||||
@ -776,7 +1102,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
||||
|
||||
// If we try to settle the invoice again, then we should get the very
|
||||
// same invoice back, but with an error this time.
|
||||
dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoice(amt))
|
||||
dbInvoice, err = db.UpdateInvoice(ref, nil, getUpdateInvoice(amt))
|
||||
if err != ErrInvoiceAlreadySettled {
|
||||
t.Fatalf("expected ErrInvoiceAlreadySettled")
|
||||
}
|
||||
@ -824,7 +1150,7 @@ func TestQueryInvoices(t *testing.T) {
|
||||
// We'll only settle half of all invoices created.
|
||||
if i%2 == 0 {
|
||||
ref := InvoiceRefByHash(paymentHash)
|
||||
_, err := db.UpdateInvoice(ref, getUpdateInvoice(amt))
|
||||
_, err := db.UpdateInvoice(ref, nil, getUpdateInvoice(amt))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to settle invoice: %v", err)
|
||||
}
|
||||
@ -1140,7 +1466,7 @@ func TestCustomRecords(t *testing.T) {
|
||||
}
|
||||
|
||||
ref := InvoiceRefByHash(paymentHash)
|
||||
_, err = db.UpdateInvoice(ref,
|
||||
_, err = db.UpdateInvoice(ref, nil,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
@ -1192,6 +1518,10 @@ func testInvoiceHtlcAMPFields(t *testing.T, isAMP bool) {
|
||||
testInvoice, err := randInvoice(1000)
|
||||
require.Nil(t, err)
|
||||
|
||||
if isAMP {
|
||||
testInvoice.Terms.Features = ampFeatures
|
||||
}
|
||||
|
||||
payHash := testInvoice.Terms.PaymentPreimage.Hash()
|
||||
_, err = db.AddInvoice(testInvoice, payHash)
|
||||
require.Nil(t, err)
|
||||
@ -1213,7 +1543,7 @@ func testInvoiceHtlcAMPFields(t *testing.T, isAMP bool) {
|
||||
}
|
||||
|
||||
ref := InvoiceRefByHash(payHash)
|
||||
_, err = db.UpdateInvoice(ref,
|
||||
_, err = db.UpdateInvoice(ref, nil,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
@ -1393,6 +1723,10 @@ func TestSetIDIndex(t *testing.T) {
|
||||
invoice, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Set AMP-specific features so that we can settle with HTLC-level
|
||||
// preimages.
|
||||
invoice.Terms.Features = ampFeatures
|
||||
|
||||
preimage := *invoice.Terms.PaymentPreimage
|
||||
payHash := preimage.Hash()
|
||||
_, err = db.AddInvoice(invoice, payHash)
|
||||
@ -1403,17 +1737,27 @@ func TestSetIDIndex(t *testing.T) {
|
||||
// Update the invoice with an accepted HTLC that also accepts the
|
||||
// invoice.
|
||||
ref := InvoiceRefByHashAndAddr(payHash, invoice.Terms.PaymentAddr)
|
||||
dbInvoice, err := db.UpdateInvoice(ref, updateAcceptAMPHtlc(0, amt, setID, true))
|
||||
dbInvoice, err := db.UpdateInvoice(
|
||||
ref, (*SetID)(setID), updateAcceptAMPHtlc(0, amt, setID, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// We'll update what we expect the accepted invoice to be so that our
|
||||
// comparison below has the correct assumption.
|
||||
invoice.State = ContractAccepted
|
||||
invoice.State = ContractOpen
|
||||
invoice.AmtPaid = amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
{HtlcID: 0}: makeAMPInvoiceHTLC(amt, *setID, payHash, &preimage),
|
||||
}
|
||||
invoice.AMPState = map[SetID]InvoiceStateAMP{}
|
||||
invoice.AMPState[*setID] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
}
|
||||
|
||||
// We should get back the exact same invoice that we just inserted.
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
@ -1429,26 +1773,36 @@ func TestSetIDIndex(t *testing.T) {
|
||||
invoice2, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Set AMP-specific features so that we can settle with HTLC-level
|
||||
// preimages.
|
||||
invoice2.Terms.Features = ampFeatures
|
||||
|
||||
payHash2 := invoice2.Terms.PaymentPreimage.Hash()
|
||||
_, err = db.AddInvoice(invoice2, payHash2)
|
||||
require.Nil(t, err)
|
||||
|
||||
ref2 := InvoiceRefByHashAndAddr(payHash2, invoice2.Terms.PaymentAddr)
|
||||
_, err = db.UpdateInvoice(ref2, updateAcceptAMPHtlc(0, amt, setID, true))
|
||||
_, err = db.UpdateInvoice(
|
||||
ref2, (*SetID)(setID), updateAcceptAMPHtlc(0, amt, setID, true),
|
||||
)
|
||||
require.Equal(t, ErrDuplicateSetID{setID: *setID}, err)
|
||||
|
||||
// Now, begin constructing a second htlc set under a different set id.
|
||||
// This set will contain two distinct HTLCs.
|
||||
setID2 := &[32]byte{2}
|
||||
|
||||
_, err = db.UpdateInvoice(ref, updateAcceptAMPHtlc(1, amt, setID2, false))
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2), updateAcceptAMPHtlc(1, amt, setID2, false),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
dbInvoice, err = db.UpdateInvoice(ref, updateAcceptAMPHtlc(2, amt, setID2, false))
|
||||
dbInvoice, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2), updateAcceptAMPHtlc(2, amt, setID2, false),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// We'll update what we expect the settle invoice to be so that our
|
||||
// comparison below has the correct assumption.
|
||||
invoice.State = ContractAccepted
|
||||
invoice.State = ContractOpen
|
||||
invoice.AmtPaid += 2 * amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
@ -1456,6 +1810,27 @@ func TestSetIDIndex(t *testing.T) {
|
||||
{HtlcID: 1}: makeAMPInvoiceHTLC(amt, *setID2, payHash, nil),
|
||||
{HtlcID: 2}: makeAMPInvoiceHTLC(amt, *setID2, payHash, nil),
|
||||
}
|
||||
invoice.AMPState[*setID] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
}
|
||||
invoice.AMPState[*setID2] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt * 2,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 1}: {},
|
||||
{HtlcID: 2}: {},
|
||||
},
|
||||
}
|
||||
|
||||
// Since UpdateInvoice will only return the sub-set of updated HTLcs,
|
||||
// we'll query again to ensure we get the full set of HTLCs returned.
|
||||
freshInvoice, err := db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
// We should get back the exact same invoice that we just inserted.
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
@ -1467,28 +1842,85 @@ func TestSetIDIndex(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, invoice, &dbInvoiceBySetID)
|
||||
|
||||
// Now settle the first htlc set, asserting that the two htlcs with set
|
||||
// id 2 get canceled as a result.
|
||||
// Now attempt to settle a non-existent HTLC set, this set ID is the
|
||||
// zero setID so it isn't used for anything internally.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, getUpdateInvoiceAMPSettle(&[32]byte{}),
|
||||
ref, nil,
|
||||
getUpdateInvoiceAMPSettle(&[32]byte{}, [32]byte{}, CircuitKey{HtlcID: 99}),
|
||||
)
|
||||
require.Equal(t, ErrEmptyHTLCSet, err)
|
||||
|
||||
// Now settle the first htlc set, asserting that the two htlcs with set
|
||||
// id 2 get canceled as a result.
|
||||
dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoiceAMPSettle(setID))
|
||||
// Now settle the first htlc set. The existing HTLCs should remain in
|
||||
// the accepted state and shouldn't be canceled, since we permit an
|
||||
// invoice to be settled multiple times.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID),
|
||||
getUpdateInvoiceAMPSettle(setID, preimage, CircuitKey{HtlcID: 0}),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
invoice.State = ContractSettled
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.SettleIndex = 1
|
||||
invoice.AmtPaid = amt
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].ResolveTime = time.Unix(1, 0)
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
invoice.State = ContractOpen
|
||||
|
||||
// The amount paid should reflect that we have 3 present HTLCs, each
|
||||
// with an amount of the original invoice.
|
||||
invoice.AmtPaid = amt * 3
|
||||
|
||||
ampState := invoice.AMPState[*setID]
|
||||
ampState.State = HtlcStateSettled
|
||||
ampState.SettleDate = testNow
|
||||
ampState.SettleIndex = 1
|
||||
|
||||
invoice.AMPState[*setID] = ampState
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].ResolveTime = time.Unix(1, 0)
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// If we try to settle the same set ID again, then we should get an
|
||||
// error, as it's already been settled.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID),
|
||||
getUpdateInvoiceAMPSettle(setID, preimage, CircuitKey{HtlcID: 0}),
|
||||
)
|
||||
require.Equal(t, ErrEmptyHTLCSet, err)
|
||||
|
||||
// Next, let's attempt to settle the other active set ID for this
|
||||
// invoice. This will allow us to exercise the case where we go to
|
||||
// settle an invoice with a new setID after one has already been fully
|
||||
// settled.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, (*SetID)(setID2),
|
||||
getUpdateInvoiceAMPSettle(
|
||||
setID2, preimage, CircuitKey{HtlcID: 1}, CircuitKey{HtlcID: 2},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
// Now the rest of the HTLCs should show as fully settled.
|
||||
ampState = invoice.AMPState[*setID2]
|
||||
ampState.State = HtlcStateSettled
|
||||
ampState.SettleDate = testNow
|
||||
ampState.SettleIndex = 2
|
||||
|
||||
invoice.AMPState[*setID2] = ampState
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].AMP.Preimage = &preimage
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].AMP.Preimage = &preimage
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// Lastly, querying for an unknown set id should fail.
|
||||
@ -1559,17 +1991,25 @@ func updateAcceptAMPHtlc(id uint64, amt lnwire.MilliSatoshi,
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateInvoiceAMPSettle(setID *[32]byte) InvoiceUpdateCallback {
|
||||
func getUpdateInvoiceAMPSettle(setID *[32]byte,
|
||||
preimage [32]byte, circuitKeys ...CircuitKey) InvoiceUpdateCallback {
|
||||
|
||||
return func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
if invoice.State == ContractSettled {
|
||||
return nil, ErrInvoiceAlreadySettled
|
||||
}
|
||||
|
||||
preImageSet := make(map[CircuitKey]lntypes.Preimage)
|
||||
for _, key := range circuitKeys {
|
||||
preImageSet[key] = preimage
|
||||
}
|
||||
|
||||
update := &InvoiceUpdateDesc{
|
||||
State: &InvoiceStateUpdateDesc{
|
||||
Preimage: nil,
|
||||
NewState: ContractSettled,
|
||||
SetID: setID,
|
||||
Preimage: nil,
|
||||
NewState: ContractSettled,
|
||||
SetID: setID,
|
||||
HTLCPreimages: preImageSet,
|
||||
},
|
||||
}
|
||||
|
||||
@ -1601,7 +2041,7 @@ func TestUnexpectedInvoicePreimage(t *testing.T) {
|
||||
// in order to settle an MPP invoice, the InvoiceRef must present a
|
||||
// payment hash against which to validate the preimage.
|
||||
_, err = db.UpdateInvoice(
|
||||
InvoiceRefByAddr(invoice.Terms.PaymentAddr),
|
||||
InvoiceRefByAddr(invoice.Terms.PaymentAddr), nil,
|
||||
getUpdateInvoice(invoice.Terms.Value),
|
||||
)
|
||||
|
||||
@ -1666,7 +2106,9 @@ func testUpdateHTLCPreimages(t *testing.T, test updateHTLCPreimageTestCase) {
|
||||
// Update the invoice with an accepted HTLC that also accepts the
|
||||
// invoice.
|
||||
ref := InvoiceRefByAddr(invoice.Terms.PaymentAddr)
|
||||
dbInvoice, err := db.UpdateInvoice(ref, updateAcceptAMPHtlc(0, amt, setID, true))
|
||||
dbInvoice, err := db.UpdateInvoice(
|
||||
ref, (*SetID)(setID), updateAcceptAMPHtlc(0, amt, setID, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
htlcPreimages := make(map[CircuitKey]lntypes.Preimage)
|
||||
@ -1694,7 +2136,7 @@ func testUpdateHTLCPreimages(t *testing.T, test updateHTLCPreimageTestCase) {
|
||||
}
|
||||
|
||||
// Now settle the HTLC set and assert the resulting error.
|
||||
_, err = db.UpdateInvoice(ref, updateInvoice)
|
||||
_, err = db.UpdateInvoice(ref, (*SetID)(setID), updateInvoice)
|
||||
require.Equal(t, test.expError, err)
|
||||
}
|
||||
|
||||
@ -2059,6 +2501,10 @@ func TestUpdateHTLC(t *testing.T) {
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
// With the newer AMP logic, this is now valid, as we
|
||||
// want to be able to accept multiple settle attempts
|
||||
// to a given pay_addr. In this case, the HTLC should
|
||||
// remain in the accepted state.
|
||||
name: "AMP settle valid preimage different htlc set",
|
||||
input: InvoiceHTLC{
|
||||
Amt: 5000,
|
||||
@ -2082,9 +2528,9 @@ func TestUpdateHTLC(t *testing.T) {
|
||||
MppTotalAmt: 5000,
|
||||
AcceptHeight: 100,
|
||||
AcceptTime: testNow,
|
||||
ResolveTime: testNow,
|
||||
ResolveTime: time.Time{},
|
||||
Expiry: 40,
|
||||
State: HtlcStateCanceled,
|
||||
State: HtlcStateAccepted,
|
||||
CustomRecords: make(record.CustomSet),
|
||||
AMP: &InvoiceHtlcAMPData{
|
||||
Record: *ampRecord,
|
||||
@ -2358,7 +2804,7 @@ func TestUpdateHTLC(t *testing.T) {
|
||||
|
||||
func testUpdateHTLC(t *testing.T, test updateHTLCTest) {
|
||||
htlc := test.input.Copy()
|
||||
err := updateHtlc(testNow, htlc, test.invState, test.setID)
|
||||
_, err := updateHtlc(testNow, htlc, test.invState, test.setID)
|
||||
require.Equal(t, test.expErr, err)
|
||||
require.Equal(t, test.output, *htlc)
|
||||
}
|
||||
@ -2387,7 +2833,7 @@ func TestDeleteInvoices(t *testing.T) {
|
||||
// Settle the second invoice.
|
||||
if i == 1 {
|
||||
invoice, err = db.UpdateInvoice(
|
||||
InvoiceRefByHash(paymentHash),
|
||||
InvoiceRefByHash(paymentHash), nil,
|
||||
getUpdateInvoice(invoice.Terms.Value),
|
||||
)
|
||||
require.NoError(t, err, "unable to settle invoice")
|
||||
@ -2445,7 +2891,6 @@ func TestDeleteInvoices(t *testing.T) {
|
||||
// Delete should succeed with all the valid references.
|
||||
require.NoError(t, db.DeleteInvoice(invoicesToDelete))
|
||||
assertInvoiceCount(0)
|
||||
|
||||
}
|
||||
|
||||
// TestAddInvoiceInvalidFeatureDeps asserts that inserting an invoice with
|
||||
@ -2474,3 +2919,92 @@ func TestAddInvoiceInvalidFeatureDeps(t *testing.T) {
|
||||
lnwire.PaymentAddrOptional,
|
||||
))
|
||||
}
|
||||
|
||||
// TestEncodeDecodeAmpInvoiceState asserts that the nested TLV
|
||||
// encoding+decoding for the AMPInvoiceState struct works as expected.
|
||||
func TestEncodeDecodeAmpInvoiceState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setID1 := [32]byte{1}
|
||||
setID2 := [32]byte{2}
|
||||
setID3 := [32]byte{3}
|
||||
|
||||
circuitKey1 := CircuitKey{
|
||||
ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 1,
|
||||
}
|
||||
circuitKey2 := CircuitKey{
|
||||
ChanID: lnwire.NewShortChanIDFromInt(2), HtlcID: 2,
|
||||
}
|
||||
circuitKey3 := CircuitKey{
|
||||
ChanID: lnwire.NewShortChanIDFromInt(2), HtlcID: 3,
|
||||
}
|
||||
|
||||
// Make a sample invoice state map that we'll encode then decode to
|
||||
// assert equality of.
|
||||
ampState := AMPInvoiceState{
|
||||
setID1: InvoiceStateAMP{
|
||||
State: HtlcStateSettled,
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 1,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: {},
|
||||
circuitKey2: {},
|
||||
},
|
||||
AmtPaid: 5,
|
||||
},
|
||||
setID2: InvoiceStateAMP{
|
||||
State: HtlcStateCanceled,
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 2,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: {},
|
||||
},
|
||||
AmtPaid: 6,
|
||||
},
|
||||
setID3: InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 3,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: {},
|
||||
circuitKey2: {},
|
||||
circuitKey3: {},
|
||||
},
|
||||
AmtPaid: 7,
|
||||
},
|
||||
}
|
||||
|
||||
// We'll now make a sample invoice stream, and use that to encode the
|
||||
// amp state we created above.
|
||||
tlvStream, err := tlv.NewStream(
|
||||
tlv.MakeDynamicRecord(
|
||||
invoiceAmpStateType, &State, ampState.recordSize,
|
||||
ampStateEncoder, ampStateDecoder,
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Next encode the stream into a set of raw bytes.
|
||||
var b bytes.Buffer
|
||||
err = tlvStream.Encode(&b)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Now create a new blank ampState map, which we'll use to decode the
|
||||
// bytes into.
|
||||
ampState2 := make(AMPInvoiceState)
|
||||
|
||||
// Decode from the raw stream into this blank mpa.
|
||||
tlvStream, err = tlv.NewStream(
|
||||
tlv.MakeDynamicRecord(
|
||||
invoiceAmpStateType, &State2, nil,
|
||||
ampStateEncoder, ampStateDecoder,
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
err = tlvStream.Decode(&b)
|
||||
require.Nil(t, err)
|
||||
|
||||
// The two states should match.
|
||||
require.Equal(t, ampState, ampState2)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -125,6 +125,28 @@ absolute height of the chain, to be met before being able to sweep their funds,
|
||||
on top of the usual CSV delay requirement. See the linked pull request for more
|
||||
details.
|
||||
|
||||
### Re-Usable Static AMP Invoices
|
||||
|
||||
[AMP invoices are now fully re-usable, meaning it's possible for an `lnd` node
|
||||
today a static AMP invoice multiple times](https://github.com/lightningnetwork/lnd/pull/5803).
|
||||
An AMP invoice can be created by adding the `--amp` flag to `lncli addinvoice`.
|
||||
From there repeated payments can be made to the invoice using `lncli
|
||||
payinvoice`. On the receiver side, notifications will still come in as normal,
|
||||
but notifying only the _new_ "sub-invoice" paid each time.
|
||||
|
||||
A new field has been added to the main `Invoice` proto that allows callers to
|
||||
easily scan to see the current state of all repeated payments to a given
|
||||
`payment_addr`.
|
||||
|
||||
A new `LookupInvoiceV2` RPC has been added to the `invoicerpcserver` which
|
||||
allows callers to look up an AMP invoice by set_id, opting to only return
|
||||
relevant HTLCs, or to look up an AMP invoice by its `payment_addr`, but omit
|
||||
all HTLC information. The first option is useful when a caller wants to get
|
||||
information specific to a repeated payment, omitting the thousands of possible
|
||||
_other_ payment attempts. The second option is useful when a caller wants to
|
||||
obtain the _base_ invoice information, and then use the first option to extract
|
||||
the custom records (or other details) of the prior payment attempts.
|
||||
|
||||
## RPC Server
|
||||
|
||||
* [Return payment address and add index from
|
||||
|
@ -236,7 +236,6 @@ func (i *InvoiceRegistry) Start() error {
|
||||
// Start InvoiceExpiryWatcher and prepopulate it with existing active
|
||||
// invoices.
|
||||
err := i.expiryWatcher.Start(i.cancelInvoiceImpl)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -273,6 +272,7 @@ func (i *InvoiceRegistry) Stop() error {
|
||||
type invoiceEvent struct {
|
||||
hash lntypes.Hash
|
||||
invoice *channeldb.Invoice
|
||||
setID *[32]byte
|
||||
}
|
||||
|
||||
// tickAt returns a channel that ticks at the specified time. If the time has
|
||||
@ -422,8 +422,10 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
continue
|
||||
|
||||
// Similarly, if we've already sent this add to
|
||||
// the client then we can skip this one.
|
||||
case state == channeldb.ContractOpen &&
|
||||
// the client then we can skip this one, but only if this isn't
|
||||
// an AMP invoice. AMP invoices always remain in the settle
|
||||
// state as a base invoice.
|
||||
case event.setID == nil && state == channeldb.ContractOpen &&
|
||||
client.addIndex >= invoice.AddIndex:
|
||||
continue
|
||||
|
||||
@ -450,6 +452,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
select {
|
||||
case client.ntfnQueue.ChanIn() <- &invoiceEvent{
|
||||
invoice: invoice,
|
||||
setID: event.setID,
|
||||
}:
|
||||
case <-i.quit:
|
||||
return
|
||||
@ -459,11 +462,24 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
// the latest add/settle index it has. We'll use this to ensure
|
||||
// we don't send a notification twice, which can happen if a new
|
||||
// event is added while we're catching up a new client.
|
||||
switch event.invoice.State {
|
||||
case channeldb.ContractSettled:
|
||||
invState := event.invoice.State
|
||||
switch {
|
||||
|
||||
case invState == channeldb.ContractSettled:
|
||||
client.settleIndex = invoice.SettleIndex
|
||||
case channeldb.ContractOpen:
|
||||
|
||||
case invState == channeldb.ContractOpen && event.setID == nil:
|
||||
client.addIndex = invoice.AddIndex
|
||||
|
||||
// If this is an AMP invoice, then we'll need to use the set ID
|
||||
// to keep track of the settle index of the client. AMP
|
||||
// invoices never go to the open state, but if a setID is
|
||||
// passed, then we know it was just settled and will track the
|
||||
// highest settle index so far.
|
||||
case invState == channeldb.ContractOpen && event.setID != nil:
|
||||
setID := *event.setID
|
||||
client.settleIndex = invoice.AMPState[setID].SettleIndex
|
||||
|
||||
default:
|
||||
log.Errorf("unexpected invoice state: %v",
|
||||
event.invoice.State)
|
||||
@ -579,7 +595,7 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
|
||||
|
||||
// Now that we've added the invoice, we'll send dispatch a message to
|
||||
// notify the clients of this new invoice.
|
||||
i.notifyClients(paymentHash, invoice)
|
||||
i.notifyClients(paymentHash, invoice, nil)
|
||||
i.Unlock()
|
||||
|
||||
// InvoiceExpiryWatcher.AddInvoice must not be locked by InvoiceRegistry
|
||||
@ -606,6 +622,14 @@ func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||
return i.cdb.LookupInvoice(ref)
|
||||
}
|
||||
|
||||
// LookupInvoiceByRef looks up an invoice by the given reference, if found
|
||||
// then we're able to pull the funds pending within an HTLC.
|
||||
func (i *InvoiceRegistry) LookupInvoiceByRef(
|
||||
ref channeldb.InvoiceRef) (channeldb.Invoice, error) {
|
||||
|
||||
return i.cdb.LookupInvoice(ref)
|
||||
}
|
||||
|
||||
// startHtlcTimer starts a new timer via the invoice registry main loop that
|
||||
// cancels a single htlc on an invoice when the htlc hold duration has passed.
|
||||
func (i *InvoiceRegistry) startHtlcTimer(invoiceRef channeldb.InvoiceRef,
|
||||
@ -648,14 +672,39 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
}
|
||||
|
||||
// Lookup the current status of the htlc in the database.
|
||||
var (
|
||||
htlcState channeldb.HtlcState
|
||||
setID *channeldb.SetID
|
||||
)
|
||||
htlc, ok := invoice.Htlcs[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("htlc %v not found", key)
|
||||
// If this is an AMP invoice, then all the HTLCs won't
|
||||
// be read out, so we'll consult the other mapping to
|
||||
// try to find the HTLC state in question here.
|
||||
var found bool
|
||||
for ampSetID, htlcSet := range invoice.AMPState {
|
||||
ampSetID := ampSetID
|
||||
for htlcKey := range htlcSet.InvoiceKeys {
|
||||
if htlcKey == key {
|
||||
htlcState = htlcSet.State
|
||||
setID = &SetID
|
||||
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("htlc %v not found", key)
|
||||
}
|
||||
} else {
|
||||
htlcState = htlc.State
|
||||
}
|
||||
|
||||
// Cancelation is only possible if the htlc wasn't already
|
||||
// resolved.
|
||||
if htlc.State != channeldb.HtlcStateAccepted {
|
||||
if htlcState != channeldb.HtlcStateAccepted {
|
||||
log.Debugf("cancelSingleHtlc: htlc %v on invoice %v "+
|
||||
"is already resolved", key, invoiceRef)
|
||||
|
||||
@ -673,6 +722,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
|
||||
return &channeldb.InvoiceUpdateDesc{
|
||||
CancelHtlcs: canceledHtlcs,
|
||||
SetID: setID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -680,7 +730,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
// Intercept the update descriptor to set the local updated variable. If
|
||||
// no invoice update is performed, we can return early.
|
||||
var updated bool
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef,
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, nil,
|
||||
func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
@ -971,6 +1021,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
)
|
||||
invoice, err := i.cdb.UpdateInvoice(
|
||||
ctx.invoiceRef(),
|
||||
(*channeldb.SetID)(ctx.setID()),
|
||||
func(inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
@ -1079,6 +1130,8 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// the HTLCs immediately. As a result of the settle, the HTLCs
|
||||
// in other HTLC sets are automatically converted to a canceled
|
||||
// state when updating the invoice.
|
||||
//
|
||||
// TODO(roasbeef): can remove now??
|
||||
canceledHtlcSet := invoice.HTLCSetCompliment(
|
||||
setID, channeldb.HtlcStateCanceled,
|
||||
)
|
||||
@ -1117,7 +1170,6 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
if invoice.State == channeldb.ContractOpen {
|
||||
res.acceptTime = invoiceHtlc.AcceptTime
|
||||
res.autoRelease = true
|
||||
|
||||
}
|
||||
|
||||
// If we have fully accepted the set of htlcs for this invoice,
|
||||
@ -1140,7 +1192,14 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// HTLCs, we'll go ahead and notify any clients wiaiting on the invoice
|
||||
// state changes.
|
||||
if updateSubscribers {
|
||||
i.notifyClients(ctx.hash, invoice)
|
||||
// We'll add a setID onto the notification, but only if this is
|
||||
// an AMP invoice being settled.
|
||||
var setID *[32]byte
|
||||
if _, ok := resolution.(*HtlcSettleResolution); ok {
|
||||
setID = ctx.setID()
|
||||
}
|
||||
|
||||
i.notifyClients(ctx.hash, invoice, setID)
|
||||
}
|
||||
|
||||
return resolution, nil
|
||||
@ -1173,7 +1232,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
|
||||
hash := preimage.Hash()
|
||||
invoiceRef := channeldb.InvoiceRefByHash(hash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, updateInvoice)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
if err != nil {
|
||||
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
||||
preimage, err)
|
||||
@ -1201,7 +1260,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
|
||||
i.notifyHodlSubscribers(resolution)
|
||||
}
|
||||
i.notifyClients(hash, invoice)
|
||||
i.notifyClients(hash, invoice, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1257,7 +1316,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
}
|
||||
|
||||
invoiceRef := channeldb.InvoiceRefByHash(payHash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, updateInvoice)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
|
||||
// Implement idempotency by returning success if the invoice was already
|
||||
// canceled.
|
||||
@ -1294,7 +1353,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
),
|
||||
)
|
||||
}
|
||||
i.notifyClients(payHash, invoice)
|
||||
i.notifyClients(payHash, invoice, nil)
|
||||
|
||||
// Attempt to also delete the invoice if requested through the registry
|
||||
// config.
|
||||
@ -1328,11 +1387,12 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
// notifyClients notifies all currently registered invoice notification clients
|
||||
// of a newly added/settled invoice.
|
||||
func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash,
|
||||
invoice *channeldb.Invoice) {
|
||||
invoice *channeldb.Invoice, setID *[32]byte) {
|
||||
|
||||
event := &invoiceEvent{
|
||||
invoice: invoice,
|
||||
hash: hash,
|
||||
setID: setID,
|
||||
}
|
||||
|
||||
select {
|
||||
@ -1470,11 +1530,19 @@ func (i *InvoiceRegistry) SubscribeNotifications(
|
||||
|
||||
var targetChan chan *channeldb.Invoice
|
||||
state := invoiceEvent.invoice.State
|
||||
switch state {
|
||||
case channeldb.ContractOpen:
|
||||
targetChan = client.NewInvoices
|
||||
case channeldb.ContractSettled:
|
||||
switch {
|
||||
// AMP invoices never move to settled, but will
|
||||
// be sent with a set ID if an HTLC set is
|
||||
// being settled.
|
||||
case state == channeldb.ContractOpen &&
|
||||
invoiceEvent.setID != nil:
|
||||
fallthrough
|
||||
case state == channeldb.ContractSettled:
|
||||
targetChan = client.SettledInvoices
|
||||
|
||||
case state == channeldb.ContractOpen:
|
||||
targetChan = client.NewInvoices
|
||||
|
||||
default:
|
||||
log.Errorf("unknown invoice "+
|
||||
"state: %v", state)
|
||||
|
@ -176,7 +176,8 @@ func TestSettleInvoice(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if inv.AmtPaid != amtPaid+amtPaid+600 {
|
||||
t.Fatal("amount incorrect")
|
||||
t.Fatalf("amount incorrect: expected %v got %v",
|
||||
amtPaid+amtPaid+600, inv.AmtPaid)
|
||||
}
|
||||
|
||||
// Try to cancel.
|
||||
@ -1641,7 +1642,12 @@ func testSpontaneousAmpPayment(
|
||||
checkSettleSubscription := func() {
|
||||
t.Helper()
|
||||
settledInvoice := <-allSubscriptions.SettledInvoices
|
||||
require.Equal(t, settledInvoice.State, channeldb.ContractSettled)
|
||||
|
||||
// Since this is an AMP invoice, the invoice state never
|
||||
// changes, but the AMP state should show that the setID has
|
||||
// been settled.
|
||||
htlcState := settledInvoice.AMPState[setID].State
|
||||
require.Equal(t, htlcState, channeldb.HtlcStateSettled)
|
||||
}
|
||||
|
||||
// Asserts that no invoice is published on the SettledInvoices channel
|
||||
|
@ -21,6 +21,64 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type LookupModifier int32
|
||||
|
||||
const (
|
||||
// The default look up modifier, no look up behavior is changed.
|
||||
LookupModifier_DEFAULT LookupModifier = 0
|
||||
//
|
||||
//Indicates that when a look up is done based on a set_id, then only that set
|
||||
//of HTLCs related to that set ID should be returned.
|
||||
LookupModifier_HTLC_SET_ONLY LookupModifier = 1
|
||||
//
|
||||
//Indicates that when a look up is done using a payment_addr, then no HTLCs
|
||||
//related to the payment_addr should be returned. This is useful when one
|
||||
//wants to be able to obtain the set of associated setIDs with a given
|
||||
//invoice, then look up the sub-invoices "projected" by that set ID.
|
||||
LookupModifier_HTLC_SET_BLANK LookupModifier = 2
|
||||
)
|
||||
|
||||
// Enum value maps for LookupModifier.
|
||||
var (
|
||||
LookupModifier_name = map[int32]string{
|
||||
0: "DEFAULT",
|
||||
1: "HTLC_SET_ONLY",
|
||||
2: "HTLC_SET_BLANK",
|
||||
}
|
||||
LookupModifier_value = map[string]int32{
|
||||
"DEFAULT": 0,
|
||||
"HTLC_SET_ONLY": 1,
|
||||
"HTLC_SET_BLANK": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x LookupModifier) Enum() *LookupModifier {
|
||||
p := new(LookupModifier)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x LookupModifier) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (LookupModifier) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_invoicesrpc_invoices_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (LookupModifier) Type() protoreflect.EnumType {
|
||||
return &file_invoicesrpc_invoices_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x LookupModifier) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LookupModifier.Descriptor instead.
|
||||
func (LookupModifier) EnumDescriptor() ([]byte, []int) {
|
||||
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type CancelInvoiceMsg struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@ -462,6 +520,108 @@ func (x *SubscribeSingleInvoiceRequest) GetRHash() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type LookupInvoiceMsg struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// Types that are assignable to InvoiceRef:
|
||||
// *LookupInvoiceMsg_PaymentHash
|
||||
// *LookupInvoiceMsg_PaymentAddr
|
||||
// *LookupInvoiceMsg_SetId
|
||||
InvoiceRef isLookupInvoiceMsg_InvoiceRef `protobuf_oneof:"invoice_ref"`
|
||||
LookupModifier LookupModifier `protobuf:"varint,4,opt,name=lookup_modifier,json=lookupModifier,proto3,enum=invoicesrpc.LookupModifier" json:"lookup_modifier,omitempty"`
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) Reset() {
|
||||
*x = LookupInvoiceMsg{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_invoicesrpc_invoices_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LookupInvoiceMsg) ProtoMessage() {}
|
||||
|
||||
func (x *LookupInvoiceMsg) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_invoicesrpc_invoices_proto_msgTypes[7]
|
||||
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 LookupInvoiceMsg.ProtoReflect.Descriptor instead.
|
||||
func (*LookupInvoiceMsg) Descriptor() ([]byte, []int) {
|
||||
return file_invoicesrpc_invoices_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (m *LookupInvoiceMsg) GetInvoiceRef() isLookupInvoiceMsg_InvoiceRef {
|
||||
if m != nil {
|
||||
return m.InvoiceRef
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) GetPaymentHash() []byte {
|
||||
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentHash); ok {
|
||||
return x.PaymentHash
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) GetPaymentAddr() []byte {
|
||||
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_PaymentAddr); ok {
|
||||
return x.PaymentAddr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) GetSetId() []byte {
|
||||
if x, ok := x.GetInvoiceRef().(*LookupInvoiceMsg_SetId); ok {
|
||||
return x.SetId
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *LookupInvoiceMsg) GetLookupModifier() LookupModifier {
|
||||
if x != nil {
|
||||
return x.LookupModifier
|
||||
}
|
||||
return LookupModifier_DEFAULT
|
||||
}
|
||||
|
||||
type isLookupInvoiceMsg_InvoiceRef interface {
|
||||
isLookupInvoiceMsg_InvoiceRef()
|
||||
}
|
||||
|
||||
type LookupInvoiceMsg_PaymentHash struct {
|
||||
PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3,oneof"`
|
||||
}
|
||||
|
||||
type LookupInvoiceMsg_PaymentAddr struct {
|
||||
PaymentAddr []byte `protobuf:"bytes,2,opt,name=payment_addr,json=paymentAddr,proto3,oneof"`
|
||||
}
|
||||
|
||||
type LookupInvoiceMsg_SetId struct {
|
||||
SetId []byte `protobuf:"bytes,3,opt,name=set_id,json=setId,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*LookupInvoiceMsg_PaymentHash) isLookupInvoiceMsg_InvoiceRef() {}
|
||||
|
||||
func (*LookupInvoiceMsg_PaymentAddr) isLookupInvoiceMsg_InvoiceRef() {}
|
||||
|
||||
func (*LookupInvoiceMsg_SetId) isLookupInvoiceMsg_InvoiceRef() {}
|
||||
|
||||
var File_invoicesrpc_invoices_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_invoicesrpc_invoices_proto_rawDesc = []byte{
|
||||
@ -510,32 +670,54 @@ var file_invoicesrpc_invoices_proto_rawDesc = []byte{
|
||||
0x72, 0x69, 0x62, 0x65, 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x72, 0x5f, 0x68, 0x61,
|
||||
0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x72, 0x48, 0x61, 0x73, 0x68, 0x4a,
|
||||
0x04, 0x08, 0x01, 0x10, 0x02, 0x32, 0xd9, 0x02, 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, 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,
|
||||
0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0xca, 0x01, 0x0a, 0x10, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
|
||||
0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x4d, 0x73, 0x67, 0x12, 0x23, 0x0a, 0x0c, 0x70, 0x61,
|
||||
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12,
|
||||
0x23, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,
|
||||
0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, 0x06, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x05, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x44, 0x0a,
|
||||
0x0f, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65,
|
||||
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,
|
||||
}
|
||||
|
||||
var (
|
||||
@ -550,33 +732,39 @@ func file_invoicesrpc_invoices_proto_rawDescGZIP() []byte {
|
||||
return file_invoicesrpc_invoices_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_invoicesrpc_invoices_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
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_goTypes = []interface{}{
|
||||
(*CancelInvoiceMsg)(nil), // 0: invoicesrpc.CancelInvoiceMsg
|
||||
(*CancelInvoiceResp)(nil), // 1: invoicesrpc.CancelInvoiceResp
|
||||
(*AddHoldInvoiceRequest)(nil), // 2: invoicesrpc.AddHoldInvoiceRequest
|
||||
(*AddHoldInvoiceResp)(nil), // 3: invoicesrpc.AddHoldInvoiceResp
|
||||
(*SettleInvoiceMsg)(nil), // 4: invoicesrpc.SettleInvoiceMsg
|
||||
(*SettleInvoiceResp)(nil), // 5: invoicesrpc.SettleInvoiceResp
|
||||
(*SubscribeSingleInvoiceRequest)(nil), // 6: invoicesrpc.SubscribeSingleInvoiceRequest
|
||||
(*lnrpc.RouteHint)(nil), // 7: lnrpc.RouteHint
|
||||
(*lnrpc.Invoice)(nil), // 8: lnrpc.Invoice
|
||||
(LookupModifier)(0), // 0: invoicesrpc.LookupModifier
|
||||
(*CancelInvoiceMsg)(nil), // 1: invoicesrpc.CancelInvoiceMsg
|
||||
(*CancelInvoiceResp)(nil), // 2: invoicesrpc.CancelInvoiceResp
|
||||
(*AddHoldInvoiceRequest)(nil), // 3: invoicesrpc.AddHoldInvoiceRequest
|
||||
(*AddHoldInvoiceResp)(nil), // 4: invoicesrpc.AddHoldInvoiceResp
|
||||
(*SettleInvoiceMsg)(nil), // 5: invoicesrpc.SettleInvoiceMsg
|
||||
(*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
|
||||
}
|
||||
var file_invoicesrpc_invoices_proto_depIdxs = []int32{
|
||||
7, // 0: invoicesrpc.AddHoldInvoiceRequest.route_hints:type_name -> lnrpc.RouteHint
|
||||
6, // 1: invoicesrpc.Invoices.SubscribeSingleInvoice:input_type -> invoicesrpc.SubscribeSingleInvoiceRequest
|
||||
0, // 2: invoicesrpc.Invoices.CancelInvoice:input_type -> invoicesrpc.CancelInvoiceMsg
|
||||
2, // 3: invoicesrpc.Invoices.AddHoldInvoice:input_type -> invoicesrpc.AddHoldInvoiceRequest
|
||||
4, // 4: invoicesrpc.Invoices.SettleInvoice:input_type -> invoicesrpc.SettleInvoiceMsg
|
||||
8, // 5: invoicesrpc.Invoices.SubscribeSingleInvoice:output_type -> lnrpc.Invoice
|
||||
1, // 6: invoicesrpc.Invoices.CancelInvoice:output_type -> invoicesrpc.CancelInvoiceResp
|
||||
3, // 7: invoicesrpc.Invoices.AddHoldInvoice:output_type -> invoicesrpc.AddHoldInvoiceResp
|
||||
5, // 8: invoicesrpc.Invoices.SettleInvoice:output_type -> invoicesrpc.SettleInvoiceResp
|
||||
5, // [5:9] is the sub-list for method output_type
|
||||
1, // [1:5] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
9, // 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
|
||||
}
|
||||
|
||||
func init() { file_invoicesrpc_invoices_proto_init() }
|
||||
@ -669,19 +857,37 @@ func file_invoicesrpc_invoices_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_invoicesrpc_invoices_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*LookupInvoiceMsg); 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),
|
||||
(*LookupInvoiceMsg_PaymentAddr)(nil),
|
||||
(*LookupInvoiceMsg_SetId)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_invoicesrpc_invoices_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumEnums: 1,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_invoicesrpc_invoices_proto_goTypes,
|
||||
DependencyIndexes: file_invoicesrpc_invoices_proto_depIdxs,
|
||||
EnumInfos: file_invoicesrpc_invoices_proto_enumTypes,
|
||||
MessageInfos: file_invoicesrpc_invoices_proto_msgTypes,
|
||||
}.Build()
|
||||
File_invoicesrpc_invoices_proto = out.File
|
||||
|
@ -167,6 +167,42 @@ func local_request_Invoices_SettleInvoice_0(ctx context.Context, marshaler runti
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
filter_Invoices_LookupInvoiceV2_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
|
||||
)
|
||||
|
||||
func request_Invoices_LookupInvoiceV2_0(ctx context.Context, marshaler runtime.Marshaler, client InvoicesClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq LookupInvoiceMsg
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Invoices_LookupInvoiceV2_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := client.LookupInvoiceV2(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
func local_request_Invoices_LookupInvoiceV2_0(ctx context.Context, marshaler runtime.Marshaler, server InvoicesServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
var protoReq LookupInvoiceMsg
|
||||
var metadata runtime.ServerMetadata
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Invoices_LookupInvoiceV2_0); err != nil {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
|
||||
msg, err := server.LookupInvoiceV2(ctx, &protoReq)
|
||||
return msg, metadata, err
|
||||
|
||||
}
|
||||
|
||||
// RegisterInvoicesHandlerServer registers the http handlers for service Invoices to "mux".
|
||||
// UnaryRPC :call InvoicesServer directly.
|
||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||
@ -249,6 +285,29 @@ func RegisterInvoicesHandlerServer(ctx context.Context, mux *runtime.ServeMux, s
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Invoices_LookupInvoiceV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
var stream runtime.ServerTransportStream
|
||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/invoicesrpc.Invoices/LookupInvoiceV2", runtime.WithHTTPPathPattern("/v2/invoices/lookup"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := local_request_Invoices_LookupInvoiceV2_0(rctx, inboundMarshaler, server, req, pathParams)
|
||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Invoices_LookupInvoiceV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -370,6 +429,26 @@ func RegisterInvoicesHandlerClient(ctx context.Context, mux *runtime.ServeMux, c
|
||||
|
||||
})
|
||||
|
||||
mux.Handle("GET", pattern_Invoices_LookupInvoiceV2_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/invoicesrpc.Invoices/LookupInvoiceV2", runtime.WithHTTPPathPattern("/v2/invoices/lookup"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
resp, md, err := request_Invoices_LookupInvoiceV2_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward_Invoices_LookupInvoiceV2_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -381,6 +460,8 @@ var (
|
||||
pattern_Invoices_AddHoldInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "hodl"}, ""))
|
||||
|
||||
pattern_Invoices_SettleInvoice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "settle"}, ""))
|
||||
|
||||
pattern_Invoices_LookupInvoiceV2_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "invoices", "lookup"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
@ -391,4 +472,6 @@ var (
|
||||
forward_Invoices_AddHoldInvoice_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Invoices_SettleInvoice_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_Invoices_LookupInvoiceV2_0 = runtime.ForwardResponseMessage
|
||||
)
|
||||
|
@ -139,4 +139,29 @@ func RegisterInvoicesJSONCallbacks(registry map[string]func(ctx context.Context,
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["invoicesrpc.Invoices.LookupInvoiceV2"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &LookupInvoiceMsg{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewInvoicesClient(conn)
|
||||
resp, err := client.LookupInvoiceV2(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,12 @@ service Invoices {
|
||||
settled, this call will succeed.
|
||||
*/
|
||||
rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp);
|
||||
|
||||
/*
|
||||
LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced
|
||||
using either its payment hash, payment address, or set ID.
|
||||
*/
|
||||
rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice);
|
||||
}
|
||||
|
||||
message CancelInvoiceMsg {
|
||||
@ -135,3 +141,32 @@ message SubscribeSingleInvoiceRequest {
|
||||
// Hash corresponding to the (hold) invoice to subscribe to.
|
||||
bytes r_hash = 2;
|
||||
}
|
||||
|
||||
enum LookupModifier {
|
||||
// The default look up modifier, no look up behavior is changed.
|
||||
DEFAULT = 0;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done based on a set_id, then only that set
|
||||
of HTLCs related to that set ID should be returned.
|
||||
*/
|
||||
HTLC_SET_ONLY = 1;
|
||||
|
||||
/*
|
||||
Indicates that when a look up is done using a payment_addr, then no HTLCs
|
||||
related to the payment_addr should be returned. This is useful when one
|
||||
wants to be able to obtain the set of associated setIDs with a given
|
||||
invoice, then look up the sub-invoices "projected" by that set ID.
|
||||
*/
|
||||
HTLC_SET_BLANK = 2;
|
||||
}
|
||||
|
||||
message LookupInvoiceMsg {
|
||||
oneof invoice_ref {
|
||||
bytes payment_hash = 1;
|
||||
bytes payment_addr = 2;
|
||||
bytes set_id = 3;
|
||||
}
|
||||
|
||||
LookupModifier lookup_modifier = 4;
|
||||
}
|
||||
|
@ -82,6 +82,65 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/invoices/lookup": {
|
||||
"get": {
|
||||
"summary": "LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced\nusing either its payment hash, payment address, or set ID.",
|
||||
"operationId": "Invoices_LookupInvoiceV2",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/lnrpcInvoice"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "An unexpected error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/rpcStatus"
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payment_hash",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
{
|
||||
"name": "payment_addr",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
{
|
||||
"name": "set_id",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
{
|
||||
"name": "lookup_modifier",
|
||||
"description": " - DEFAULT: The default look up modifier, no look up behavior is changed.\n - HTLC_SET_ONLY: Indicates that when a look up is done based on a set_id, then only that set\nof HTLCs related to that set ID should be returned.\n - HTLC_SET_BLANK: Indicates that when a look up is done using a payment_addr, then no HTLCs\nrelated to the payment_addr should be returned. This is useful when one\nwants to be able to obtain the set of associated setIDs with a given\ninvoice, then look up the sub-invoices \"projected\" by that set ID.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"DEFAULT",
|
||||
"HTLC_SET_ONLY",
|
||||
"HTLC_SET_BLANK"
|
||||
],
|
||||
"default": "DEFAULT"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Invoices"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/invoices/settle": {
|
||||
"post": {
|
||||
"summary": "SettleInvoice settles an accepted invoice. If the invoice is already\nsettled, this call will succeed.",
|
||||
@ -257,6 +316,16 @@
|
||||
"invoicesrpcCancelInvoiceResp": {
|
||||
"type": "object"
|
||||
},
|
||||
"invoicesrpcLookupModifier": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"DEFAULT",
|
||||
"HTLC_SET_ONLY",
|
||||
"HTLC_SET_BLANK"
|
||||
],
|
||||
"default": "DEFAULT",
|
||||
"description": " - DEFAULT: The default look up modifier, no look up behavior is changed.\n - HTLC_SET_ONLY: Indicates that when a look up is done based on a set_id, then only that set\nof HTLCs related to that set ID should be returned.\n - HTLC_SET_BLANK: Indicates that when a look up is done using a payment_addr, then no HTLCs\nrelated to the payment_addr should be returned. This is useful when one\nwants to be able to obtain the set of associated setIDs with a given\ninvoice, then look up the sub-invoices \"projected\" by that set ID."
|
||||
},
|
||||
"invoicesrpcSettleInvoiceMsg": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -301,6 +370,30 @@
|
||||
},
|
||||
"description": "Details specific to AMP HTLCs."
|
||||
},
|
||||
"lnrpcAMPInvoiceState": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"$ref": "#/definitions/lnrpcInvoiceHTLCState",
|
||||
"description": "The state the HTLCs associated with this setID are in."
|
||||
},
|
||||
"settle_index": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "The settle index of this HTLC set, if the invoice state is settled."
|
||||
},
|
||||
"settle_time": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The time this HTLC set was settled expressed in unix epoch."
|
||||
},
|
||||
"amt_paid_msat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The total amount paid for the sub-invoice expressed in milli satoshis."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcFeature": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -471,11 +564,19 @@
|
||||
"payment_addr": {
|
||||
"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."
|
||||
"description": "The payment address of this invoice. This value will be used in MPP\npayments, and also for newer invoices that always require the MPP payload\nfor added end-to-end security."
|
||||
},
|
||||
"is_amp": {
|
||||
"type": "boolean",
|
||||
"description": "Signals whether or not this is an AMP invoice."
|
||||
},
|
||||
"amp_invoice_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/lnrpcAMPInvoiceState"
|
||||
},
|
||||
"description": "Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the\ngiven set ID. This field is always populated for AMP invoices, and can be\nused along side LookupInvoice to obtain the HTLC information related to a\ngiven sub-invoice.",
|
||||
"title": "[EXPERIMENTAL]:"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -14,3 +14,5 @@ http:
|
||||
- selector: invoicesrpc.Invoices.SettleInvoice
|
||||
post: "/v2/invoices/settle"
|
||||
body: "*"
|
||||
- selector: invoicesrpc.Invoices.LookupInvoiceV2
|
||||
get: "/v2/invoices/lookup"
|
||||
|
@ -37,6 +37,10 @@ type InvoicesClient interface {
|
||||
//SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
//settled, this call will succeed.
|
||||
SettleInvoice(ctx context.Context, in *SettleInvoiceMsg, opts ...grpc.CallOption) (*SettleInvoiceResp, error)
|
||||
//
|
||||
//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)
|
||||
}
|
||||
|
||||
type invoicesClient struct {
|
||||
@ -106,6 +110,15 @@ func (c *invoicesClient) SettleInvoice(ctx context.Context, in *SettleInvoiceMsg
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *invoicesClient) LookupInvoiceV2(ctx context.Context, in *LookupInvoiceMsg, opts ...grpc.CallOption) (*lnrpc.Invoice, error) {
|
||||
out := new(lnrpc.Invoice)
|
||||
err := c.cc.Invoke(ctx, "/invoicesrpc.Invoices/LookupInvoiceV2", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// InvoicesServer is the server API for Invoices service.
|
||||
// All implementations must embed UnimplementedInvoicesServer
|
||||
// for forward compatibility
|
||||
@ -128,6 +141,10 @@ type InvoicesServer interface {
|
||||
//SettleInvoice settles an accepted invoice. If the invoice is already
|
||||
//settled, this call will succeed.
|
||||
SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error)
|
||||
//
|
||||
//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)
|
||||
mustEmbedUnimplementedInvoicesServer()
|
||||
}
|
||||
|
||||
@ -147,6 +164,9 @@ func (UnimplementedInvoicesServer) AddHoldInvoice(context.Context, *AddHoldInvoi
|
||||
func (UnimplementedInvoicesServer) SettleInvoice(context.Context, *SettleInvoiceMsg) (*SettleInvoiceResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SettleInvoice not implemented")
|
||||
}
|
||||
func (UnimplementedInvoicesServer) LookupInvoiceV2(context.Context, *LookupInvoiceMsg) (*lnrpc.Invoice, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method LookupInvoiceV2 not implemented")
|
||||
}
|
||||
func (UnimplementedInvoicesServer) mustEmbedUnimplementedInvoicesServer() {}
|
||||
|
||||
// UnsafeInvoicesServer may be embedded to opt out of forward compatibility for this service.
|
||||
@ -235,6 +255,24 @@ func _Invoices_SettleInvoice_Handler(srv interface{}, ctx context.Context, dec f
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Invoices_LookupInvoiceV2_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LookupInvoiceMsg)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(InvoicesServer).LookupInvoiceV2(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/invoicesrpc.Invoices/LookupInvoiceV2",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(InvoicesServer).LookupInvoiceV2(ctx, req.(*LookupInvoiceMsg))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -254,6 +292,10 @@ var Invoices_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SettleInvoice",
|
||||
Handler: _Invoices_SettleInvoice_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "LookupInvoiceV2",
|
||||
Handler: _Invoices_LookupInvoiceV2_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
@ -5,11 +5,14 @@ package invoicesrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
@ -59,6 +62,10 @@ var (
|
||||
Entity: "invoices",
|
||||
Action: "write",
|
||||
}},
|
||||
"/invoicesrpc.Invoices/LookupInvoiceV2": {{
|
||||
Entity: "invoices",
|
||||
Action: "write",
|
||||
}},
|
||||
}
|
||||
|
||||
// DefaultInvoicesMacFilename is the default name of the invoices
|
||||
@ -363,3 +370,71 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
|
||||
PaymentAddr: dbInvoice.Terms.PaymentAddr[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
|
||||
// using either its payment hash, payment address, or set ID.
|
||||
func (s *Server) LookupInvoiceV2(ctx context.Context,
|
||||
req *LookupInvoiceMsg) (*lnrpc.Invoice, error) {
|
||||
|
||||
var invoiceRef channeldb.InvoiceRef
|
||||
|
||||
// First, we'll attempt to parse out the invoice ref from the proto
|
||||
// oneof. If none of the three currently supported types was
|
||||
// specified, then we'll exit with an error.
|
||||
switch {
|
||||
case req.GetPaymentHash() != nil:
|
||||
payHash, err := lntypes.MakeHash(req.GetPaymentHash())
|
||||
if err != nil {
|
||||
return nil, status.Error(
|
||||
codes.InvalidArgument,
|
||||
fmt.Sprintf("unable to parse pay hash: %v", err),
|
||||
)
|
||||
}
|
||||
|
||||
invoiceRef = channeldb.InvoiceRefByHash(payHash)
|
||||
|
||||
case req.GetPaymentAddr() != nil &&
|
||||
req.LookupModifier == LookupModifier_HTLC_SET_BLANK:
|
||||
|
||||
var payAddr [32]byte
|
||||
copy(payAddr[:], req.GetPaymentAddr())
|
||||
|
||||
invoiceRef = channeldb.InvoiceRefByAddrBlankHtlc(payAddr)
|
||||
|
||||
case req.GetPaymentAddr() != nil:
|
||||
var payAddr [32]byte
|
||||
copy(payAddr[:], req.GetPaymentAddr())
|
||||
|
||||
invoiceRef = channeldb.InvoiceRefByAddr(payAddr)
|
||||
|
||||
case req.GetSetId() != nil &&
|
||||
req.LookupModifier == LookupModifier_HTLC_SET_ONLY:
|
||||
|
||||
var setID [32]byte
|
||||
copy(setID[:], req.GetSetId())
|
||||
|
||||
invoiceRef = channeldb.InvoiceRefBySetIDFiltered(setID)
|
||||
|
||||
case req.GetSetId() != nil:
|
||||
var setID [32]byte
|
||||
copy(setID[:], req.GetSetId())
|
||||
|
||||
invoiceRef = channeldb.InvoiceRefBySetID(setID)
|
||||
|
||||
default:
|
||||
return nil, status.Error(codes.InvalidArgument,
|
||||
"invoice ref must be set")
|
||||
}
|
||||
|
||||
// Attempt to locate the invoice, returning a nice "not found" error if
|
||||
// we can't find it in the database.
|
||||
invoice, err := s.cfg.InvoiceRegistry.LookupInvoiceByRef(invoiceRef)
|
||||
switch {
|
||||
case err == channeldb.ErrInvoiceNotFound:
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return CreateRPCInvoice(&invoice, s.cfg.ChainParams)
|
||||
}
|
||||
|
@ -177,6 +177,39 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
||||
IsAmp: isAmp,
|
||||
}
|
||||
|
||||
rpcInvoice.AmpInvoiceState = make(map[string]*lnrpc.AMPInvoiceState)
|
||||
for setID, ampState := range invoice.AMPState {
|
||||
|
||||
setIDStr := hex.EncodeToString(setID[:])
|
||||
|
||||
var state lnrpc.InvoiceHTLCState
|
||||
switch ampState.State {
|
||||
case channeldb.HtlcStateAccepted:
|
||||
state = lnrpc.InvoiceHTLCState_ACCEPTED
|
||||
case channeldb.HtlcStateSettled:
|
||||
state = lnrpc.InvoiceHTLCState_SETTLED
|
||||
case channeldb.HtlcStateCanceled:
|
||||
state = lnrpc.InvoiceHTLCState_CANCELED
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown state %v", ampState.State)
|
||||
}
|
||||
|
||||
rpcInvoice.AmpInvoiceState[setIDStr] = &lnrpc.AMPInvoiceState{
|
||||
State: state,
|
||||
SettleIndex: ampState.SettleIndex,
|
||||
SettleTime: ampState.SettleDate.Unix(),
|
||||
AmtPaidMsat: int64(ampState.AmtPaid),
|
||||
}
|
||||
|
||||
// If at least one of the present HTLC sets show up as being
|
||||
// settled, then we'll mark the invoice itself as being
|
||||
// settled.
|
||||
if ampState.State == channeldb.HtlcStateSettled {
|
||||
rpcInvoice.Settled = true // nolint:staticcheck
|
||||
rpcInvoice.State = lnrpc.Invoice_SETTLED
|
||||
}
|
||||
}
|
||||
|
||||
if preimage != nil {
|
||||
rpcInvoice.RPreimage = preimage[:]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3056,6 +3056,10 @@ message HopHint {
|
||||
uint32 cltv_expiry_delta = 5;
|
||||
}
|
||||
|
||||
message SetID {
|
||||
bytes set_id = 1;
|
||||
}
|
||||
|
||||
message RouteHint {
|
||||
/*
|
||||
A list of hop hints that when chained together can assist in reaching a
|
||||
@ -3064,6 +3068,20 @@ message RouteHint {
|
||||
repeated HopHint hop_hints = 1;
|
||||
}
|
||||
|
||||
message AMPInvoiceState {
|
||||
// The state the HTLCs associated with this setID are in.
|
||||
InvoiceHTLCState state = 1;
|
||||
|
||||
// The settle index of this HTLC set, if the invoice state is settled.
|
||||
uint64 settle_index = 2;
|
||||
|
||||
// The time this HTLC set was settled expressed in unix epoch.
|
||||
int64 settle_time = 3;
|
||||
|
||||
// The total amount paid for the sub-invoice expressed in milli satoshis.
|
||||
int64 amt_paid_msat = 5;
|
||||
}
|
||||
|
||||
message Invoice {
|
||||
/*
|
||||
An optional memo to attach along with the invoice. Used for record keeping
|
||||
@ -3209,7 +3227,7 @@ message Invoice {
|
||||
|
||||
/*
|
||||
The payment address of this invoice. This value will be used in MPP
|
||||
payments, and also for newer invoies that always require the MPP paylaod
|
||||
payments, and also for newer invoices that always require the MPP payload
|
||||
for added end-to-end security.
|
||||
*/
|
||||
bytes payment_addr = 26;
|
||||
@ -3218,6 +3236,16 @@ message Invoice {
|
||||
Signals whether or not this is an AMP invoice.
|
||||
*/
|
||||
bool is_amp = 27;
|
||||
|
||||
/*
|
||||
[EXPERIMENTAL]:
|
||||
|
||||
Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the
|
||||
given set ID. This field is always populated for AMP invoices, and can be
|
||||
used along side LookupInvoice to obtain the HTLC information related to a
|
||||
given sub-invoice.
|
||||
*/
|
||||
map<string, AMPInvoiceState> amp_invoice_state = 28;
|
||||
}
|
||||
|
||||
enum InvoiceHTLCState {
|
||||
|
@ -2838,6 +2838,30 @@
|
||||
},
|
||||
"description": "Details specific to AMP HTLCs."
|
||||
},
|
||||
"lnrpcAMPInvoiceState": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"$ref": "#/definitions/lnrpcInvoiceHTLCState",
|
||||
"description": "The state the HTLCs associated with this setID are in."
|
||||
},
|
||||
"settle_index": {
|
||||
"type": "string",
|
||||
"format": "uint64",
|
||||
"description": "The settle index of this HTLC set, if the invoice state is settled."
|
||||
},
|
||||
"settle_time": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The time this HTLC set was settled expressed in unix epoch."
|
||||
},
|
||||
"amt_paid_msat": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "The total amount paid for the sub-invoice expressed in milli satoshis."
|
||||
}
|
||||
}
|
||||
},
|
||||
"lnrpcAMPRecord": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -4755,11 +4779,19 @@
|
||||
"payment_addr": {
|
||||
"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."
|
||||
"description": "The payment address of this invoice. This value will be used in MPP\npayments, and also for newer invoices that always require the MPP payload\nfor added end-to-end security."
|
||||
},
|
||||
"is_amp": {
|
||||
"type": "boolean",
|
||||
"description": "Signals whether or not this is an AMP invoice."
|
||||
},
|
||||
"amp_invoice_state": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/lnrpcAMPInvoiceState"
|
||||
},
|
||||
"description": "Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the\ngiven set ID. This field is always populated for AMP invoices, and can be\nused along side LookupInvoice to obtain the HTLC information related to a\ngiven sub-invoice.",
|
||||
"title": "[EXPERIMENTAL]:"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ package itest
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@ -11,6 +12,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/amp"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
@ -200,6 +202,198 @@ func testSendPaymentAMPInvoiceCase(net *lntest.NetworkHarness, t *harnessTest,
|
||||
require.True(t.t, validPreimage)
|
||||
}
|
||||
|
||||
// The set ID we extract above should be shown in the final settled
|
||||
// state.
|
||||
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
|
||||
require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
|
||||
}
|
||||
|
||||
// testSendPaymentAMPInvoiceRepeat tests that it's possible to pay an AMP
|
||||
// invoice multiple times by having the client generate a new setID each time.
|
||||
func testSendPaymentAMPInvoiceRepeat(net *lntest.NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
// In this basic test, we'll only need two nodes as we want to
|
||||
// primarily test the recurring payment feature. So we'll re-use the
|
||||
carol := net.NewNode(t.t, "Carol", nil)
|
||||
defer shutdownAndAssert(net, t, carol)
|
||||
|
||||
// Send Carol enough coins to be able to open a channel to Dave.
|
||||
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
dave := net.NewNode(t.t, "Dave", nil)
|
||||
defer shutdownAndAssert(net, t, dave)
|
||||
|
||||
// Before we start the test, we'll ensure both sides are connected to
|
||||
// the funding flow can properly be executed.
|
||||
net.EnsureConnected(t.t, carol, dave)
|
||||
|
||||
// Set up an invoice subscription so we can be notified when Dave
|
||||
// receives his repeated payments.
|
||||
req := &lnrpc.InvoiceSubscription{}
|
||||
ctxb := context.Background()
|
||||
ctxc, cancelSubscription := context.WithCancel(ctxb)
|
||||
invSubscription, err := dave.SubscribeInvoices(ctxc, req)
|
||||
require.NoError(t.t, err)
|
||||
defer cancelSubscription()
|
||||
|
||||
// Establish a channel between Carol and Dave.
|
||||
chanAmt := btcutil.Amount(100_000)
|
||||
chanPoint := openChannelAndAssert(
|
||||
t, net, carol, dave,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||
err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
require.NoError(t.t, err, "carol didn't report channel")
|
||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||
err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||
require.NoError(t.t, err, "dave didn't report channel")
|
||||
|
||||
// Create an AMP invoice of a trivial amount, that we'll pay repeatedly
|
||||
// in this integration test.
|
||||
paymentAmt := 10000
|
||||
addInvoiceResp, err := dave.AddInvoice(ctxb, &lnrpc.Invoice{
|
||||
Value: int64(paymentAmt),
|
||||
IsAmp: true,
|
||||
})
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// We should get an initial notification that the HTLC has been added.
|
||||
rpcInvoice, err := invSubscription.Recv()
|
||||
require.NoError(t.t, err)
|
||||
require.False(t.t, rpcInvoice.Settled) // nolint:staticcheck
|
||||
require.Equal(t.t, lnrpc.Invoice_OPEN, rpcInvoice.State)
|
||||
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidSat)
|
||||
require.Equal(t.t, int64(0), rpcInvoice.AmtPaidMsat)
|
||||
|
||||
require.Equal(t.t, 0, len(rpcInvoice.Htlcs))
|
||||
|
||||
// Now we'll use Carol to pay the invoice that Dave created.
|
||||
_ = sendAndAssertSuccess(
|
||||
t, carol, &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: addInvoiceResp.PaymentRequest,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
},
|
||||
)
|
||||
|
||||
// Dave should get a notification that the invoice has been settled.
|
||||
invoiceNtfn, err := invSubscription.Recv()
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// The notification should signal that the invoice is now settled, and
|
||||
// should also include the set ID, and show the proper amount paid.
|
||||
require.True(t.t, invoiceNtfn.Settled) // nolint:staticcheck
|
||||
require.Equal(t.t, lnrpc.Invoice_SETTLED, invoiceNtfn.State)
|
||||
require.Equal(t.t, paymentAmt, int(invoiceNtfn.AmtPaidSat))
|
||||
require.Equal(t.t, 1, len(invoiceNtfn.AmpInvoiceState))
|
||||
var firstSetID []byte
|
||||
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
|
||||
firstSetID, _ = hex.DecodeString(setIDStr)
|
||||
require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
|
||||
}
|
||||
|
||||
// Pay the invoice again, we should get another notification that Dave
|
||||
// has received another payment.
|
||||
_ = sendAndAssertSuccess(
|
||||
t, carol, &routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: addInvoiceResp.PaymentRequest,
|
||||
TimeoutSeconds: 60,
|
||||
FeeLimitMsat: noFeeLimitMsat,
|
||||
},
|
||||
)
|
||||
|
||||
// Dave should get another notification.
|
||||
invoiceNtfn, err = invSubscription.Recv()
|
||||
require.NoError(t.t, err)
|
||||
|
||||
// The invoice should still be shown as settled, and also include the
|
||||
// information about this newly generated setID, showing 2x the amount
|
||||
// paid.
|
||||
require.True(t.t, invoiceNtfn.Settled) // nolint:staticcheck
|
||||
require.Equal(t.t, paymentAmt*2, int(invoiceNtfn.AmtPaidSat))
|
||||
|
||||
var secondSetID []byte
|
||||
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
|
||||
secondSetID, _ = hex.DecodeString(setIDStr)
|
||||
require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
|
||||
}
|
||||
|
||||
// The returned invoice should only include a single HTLC since we
|
||||
// return the "projected" sub-invoice for a given setID.
|
||||
require.Equal(t.t, 1, len(invoiceNtfn.Htlcs))
|
||||
|
||||
// However the AMP state index should show that there've been two
|
||||
// repeated payments to this invoice so far.
|
||||
require.Equal(t.t, 2, len(invoiceNtfn.AmpInvoiceState))
|
||||
|
||||
// Now we'll look up the invoice using the new LookupInvoice2 RPC call
|
||||
// by the set ID of each of the invoices.
|
||||
subInvoice1, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{
|
||||
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
|
||||
SetId: firstSetID,
|
||||
},
|
||||
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
|
||||
})
|
||||
require.Nil(t.t, err)
|
||||
subInvoice2, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{
|
||||
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
|
||||
SetId: secondSetID,
|
||||
},
|
||||
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
|
||||
})
|
||||
require.Nil(t.t, err)
|
||||
|
||||
// Each invoice should only show a single HTLC present, as we passed
|
||||
// the HTLC set only modifier.
|
||||
require.Equal(t.t, 1, len(subInvoice1.Htlcs))
|
||||
require.Equal(t.t, 1, len(subInvoice2.Htlcs))
|
||||
|
||||
// If we look up the same invoice, by its payment address, but now with
|
||||
// the HTLC blank modifier, then none of them should be returned.
|
||||
rootInvoice, err := dave.LookupInvoiceV2(ctxb, &invoicesrpc.LookupInvoiceMsg{
|
||||
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
|
||||
PaymentAddr: addInvoiceResp.PaymentAddr,
|
||||
},
|
||||
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_BLANK,
|
||||
})
|
||||
require.Nil(t.t, err)
|
||||
require.Equal(t.t, 0, len(rootInvoice.Htlcs))
|
||||
|
||||
// If we look up the same invoice, by its payment address, but without
|
||||
// that modified, then we should get all the relevant HTLCs.
|
||||
rootInvoice, err = dave.LookupInvoiceV2(ctxb,
|
||||
&invoicesrpc.LookupInvoiceMsg{
|
||||
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
|
||||
PaymentAddr: addInvoiceResp.PaymentAddr,
|
||||
},
|
||||
})
|
||||
require.Nil(t.t, err)
|
||||
require.Equal(t.t, 2, len(rootInvoice.Htlcs))
|
||||
|
||||
// Finally, we'll test that if we subscribe for notifications of
|
||||
// settled invoices, we get a backlog, which includes the invoice we
|
||||
// settled last (since you can only fetch from index 1 onwards), and
|
||||
// only the relevant set of HTLCs.
|
||||
req = &lnrpc.InvoiceSubscription{
|
||||
SettleIndex: 1,
|
||||
}
|
||||
ctxc, cancelSubscription2 := context.WithCancel(ctxb)
|
||||
invSub2, err := dave.SubscribeInvoices(ctxc, req)
|
||||
require.NoError(t.t, err)
|
||||
defer cancelSubscription2()
|
||||
|
||||
// The first invoice we get back should match the state of the invoice
|
||||
// after our second payment: amt updated, but only a single HTLC shown
|
||||
// through.
|
||||
backlogInv, _ := invSub2.Recv()
|
||||
require.Equal(t.t, 1, len(backlogInv.Htlcs))
|
||||
require.Equal(t.t, 2, len(backlogInv.AmpInvoiceState))
|
||||
require.True(t.t, backlogInv.Settled) // nolint:staticcheck
|
||||
require.Equal(t.t, paymentAmt*2, int(backlogInv.AmtPaidSat))
|
||||
}
|
||||
|
||||
// testSendPaymentAMP tests that we can send an AMP payment to a specified
|
||||
@ -320,6 +514,11 @@ func testSendPaymentAMP(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
validPreimage := childPreimage.Matches(childHash)
|
||||
require.True(t.t, validPreimage)
|
||||
}
|
||||
|
||||
// The set ID we extract above should be shown in the final settled
|
||||
// state.
|
||||
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
|
||||
require.Equal(t.t, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
|
||||
}
|
||||
|
||||
func testSendToRouteAMP(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
|
@ -303,6 +303,10 @@ var allTestCases = []*testCase{
|
||||
name: "sendpayment amp invoice",
|
||||
test: testSendPaymentAMPInvoice,
|
||||
},
|
||||
{
|
||||
name: "sendpayment amp invoice repeat",
|
||||
test: testSendPaymentAMPInvoiceRepeat,
|
||||
},
|
||||
{
|
||||
name: "send multi path payment",
|
||||
test: testSendMultiPathPayment,
|
||||
|
Loading…
x
Reference in New Issue
Block a user