mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 22:25:24 +01:00
invoices: store custom records in invoice database
This commit is contained in:
parent
37258c414c
commit
5f4bd136cd
10 changed files with 725 additions and 584 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
@ -209,13 +210,15 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
|
|||
|
||||
// Accept an htlc on this invoice.
|
||||
key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4}
|
||||
htlc := HtlcAcceptDesc{
|
||||
Amt: 500,
|
||||
CustomRecords: make(hop.CustomRecordSet),
|
||||
}
|
||||
invoice, err := db.UpdateInvoice(paymentHash,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
key: {
|
||||
Amt: 500,
|
||||
},
|
||||
key: &htlc,
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
|
@ -432,10 +435,11 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
|||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
{}: {
|
||||
Amt: amt,
|
||||
AcceptTime: time.Unix(1, 0),
|
||||
ResolveTime: time.Unix(1, 0),
|
||||
State: HtlcStateSettled,
|
||||
Amt: amt,
|
||||
AcceptTime: time.Unix(1, 0),
|
||||
ResolveTime: time.Unix(1, 0),
|
||||
State: HtlcStateSettled,
|
||||
CustomRecords: make(hop.CustomRecordSet),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -747,6 +751,8 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
|
|||
return nil, ErrInvoiceAlreadySettled
|
||||
}
|
||||
|
||||
noRecords := make(hop.CustomRecordSet)
|
||||
|
||||
update := &InvoiceUpdateDesc{
|
||||
State: &InvoiceStateUpdateDesc{
|
||||
Preimage: invoice.Terms.PaymentPreimage,
|
||||
|
@ -754,7 +760,8 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
|
|||
},
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
{}: {
|
||||
Amt: amt,
|
||||
Amt: amt,
|
||||
CustomRecords: noRecords,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -762,3 +769,64 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
|
|||
return update, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TestCustomRecords tests that custom records are properly recorded in the
|
||||
// invoice database.
|
||||
func TestCustomRecords(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, cleanUp, err := makeTestDB()
|
||||
defer cleanUp()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make test db: %v", err)
|
||||
}
|
||||
|
||||
testInvoice := &Invoice{
|
||||
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||
}
|
||||
testInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)
|
||||
testInvoice.Terms.Features = emptyFeatures
|
||||
|
||||
var paymentHash lntypes.Hash
|
||||
if _, err := db.AddInvoice(testInvoice, paymentHash); err != nil {
|
||||
t.Fatalf("unable to find invoice: %v", err)
|
||||
}
|
||||
|
||||
// Accept an htlc with custom records on this invoice.
|
||||
key := CircuitKey{ChanID: lnwire.NewShortChanIDFromInt(1), HtlcID: 4}
|
||||
|
||||
records := hop.CustomRecordSet{
|
||||
100000: []byte{},
|
||||
100001: []byte{1, 2},
|
||||
}
|
||||
|
||||
_, err = db.UpdateInvoice(paymentHash,
|
||||
func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
return &InvoiceUpdateDesc{
|
||||
AddHtlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
key: {
|
||||
Amt: 500,
|
||||
CustomRecords: records,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add invoice htlc: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve the invoice from that database and verify that the custom
|
||||
// records are present.
|
||||
dbInvoice, err := db.LookupInvoice(paymentHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to lookup invoice: %v", err)
|
||||
}
|
||||
|
||||
if len(dbInvoice.Htlcs) != 1 {
|
||||
t.Fatalf("expected the htlc to be added")
|
||||
}
|
||||
if !reflect.DeepEqual(records, dbInvoice.Htlcs[key].CustomRecords) {
|
||||
t.Fatalf("invalid custom records")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
|
@ -308,6 +309,10 @@ type InvoiceHTLC struct {
|
|||
// canceled htlc isn't just removed from the invoice htlcs map, because
|
||||
// we need AcceptHeight to properly cancel the htlc back.
|
||||
State HtlcState
|
||||
|
||||
// CustomRecords contains the custom key/value pairs that accompanied
|
||||
// the htlc.
|
||||
CustomRecords hop.CustomRecordSet
|
||||
}
|
||||
|
||||
// HtlcAcceptDesc describes the details of a newly accepted htlc.
|
||||
|
@ -320,6 +325,10 @@ type HtlcAcceptDesc struct {
|
|||
|
||||
// Expiry is the expiry height of this htlc.
|
||||
Expiry uint32
|
||||
|
||||
// CustomRecords contains the custom key/value pairs that accompanied
|
||||
// the htlc.
|
||||
CustomRecords hop.CustomRecordSet
|
||||
}
|
||||
|
||||
// InvoiceUpdateDesc describes the changes that should be applied to the
|
||||
|
@ -1013,7 +1022,8 @@ func serializeHtlcs(w io.Writer, htlcs map[CircuitKey]*InvoiceHTLC) error {
|
|||
resolveTime := uint64(htlc.ResolveTime.UnixNano())
|
||||
state := uint8(htlc.State)
|
||||
|
||||
tlvStream, err := tlv.NewStream(
|
||||
var records []tlv.Record
|
||||
records = append(records,
|
||||
tlv.MakePrimitiveRecord(chanIDType, &chanID),
|
||||
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
|
||||
tlv.MakePrimitiveRecord(amtType, &amt),
|
||||
|
@ -1025,6 +1035,16 @@ func serializeHtlcs(w io.Writer, htlcs map[CircuitKey]*InvoiceHTLC) error {
|
|||
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
|
||||
tlv.MakePrimitiveRecord(htlcStateType, &state),
|
||||
)
|
||||
|
||||
// Convert the custom records to tlv.Record types that are ready
|
||||
// for serialization.
|
||||
customRecords := tlv.MapToRecords(htlc.CustomRecords)
|
||||
|
||||
// Append the custom records. Their ids are in the experimental
|
||||
// range and sorted, so there is no need to sort again.
|
||||
records = append(records, customRecords...)
|
||||
|
||||
tlvStream, err := tlv.NewStream(records...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1191,7 +1211,8 @@ func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(htlcReader); err != nil {
|
||||
parsedTypes, err := tlvStream.DecodeWithParsedTypes(htlcReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -1201,6 +1222,10 @@ func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) {
|
|||
htlc.State = HtlcState(state)
|
||||
htlc.Amt = lnwire.MilliSatoshi(amt)
|
||||
|
||||
// Reconstruct the custom records fields from the parsed types
|
||||
// map return from the tlv parser.
|
||||
htlc.CustomRecords = hop.NewCustomRecords(parsedTypes)
|
||||
|
||||
htlcs[key] = &htlc
|
||||
}
|
||||
|
||||
|
@ -1290,12 +1315,20 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucke
|
|||
if _, exists := invoice.Htlcs[key]; exists {
|
||||
return nil, fmt.Errorf("duplicate add of htlc %v", key)
|
||||
}
|
||||
|
||||
// Force caller to supply htlc without custom records in a
|
||||
// consistent way.
|
||||
if htlcUpdate.CustomRecords == nil {
|
||||
return nil, errors.New("nil custom records map")
|
||||
}
|
||||
|
||||
htlc := &InvoiceHTLC{
|
||||
Amt: htlcUpdate.Amt,
|
||||
Expiry: htlcUpdate.Expiry,
|
||||
AcceptHeight: uint32(htlcUpdate.AcceptHeight),
|
||||
AcceptTime: now,
|
||||
State: HtlcStateAccepted,
|
||||
Amt: htlcUpdate.Amt,
|
||||
Expiry: htlcUpdate.Expiry,
|
||||
AcceptHeight: uint32(htlcUpdate.AcceptHeight),
|
||||
AcceptTime: now,
|
||||
State: HtlcStateAccepted,
|
||||
CustomRecords: htlcUpdate.CustomRecords,
|
||||
}
|
||||
|
||||
invoice.Htlcs[key] = htlc
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package invoices
|
||||
|
||||
import "github.com/lightningnetwork/lnd/record"
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
)
|
||||
|
||||
// Payload abstracts access to any additional fields provided in the final hop's
|
||||
// TLV onion payload.
|
||||
|
@ -8,4 +11,8 @@ type Payload interface {
|
|||
// MultiPath returns the record corresponding the option_mpp parsed from
|
||||
// the onion payload.
|
||||
MultiPath() *record.MPP
|
||||
|
||||
// CustomRecords returns the custom tlv type records that were parsed
|
||||
// from the payload.
|
||||
CustomRecords() hop.CustomRecordSet
|
||||
}
|
||||
|
|
|
@ -443,6 +443,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||
expiry: expiry,
|
||||
currentHeight: currentHeight,
|
||||
finalCltvRejectDelta: i.finalCltvRejectDelta,
|
||||
customRecords: payload.CustomRecords(),
|
||||
}
|
||||
|
||||
// We'll attempt to settle an invoice matching this rHash on disk (if
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
|
@ -669,3 +670,7 @@ type mockPayload struct {
|
|||
func (p *mockPayload) MultiPath() *record.MPP {
|
||||
return p.mpp
|
||||
}
|
||||
|
||||
func (p *mockPayload) CustomRecords() hop.CustomRecordSet {
|
||||
return make(hop.CustomRecordSet)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package invoices
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
@ -74,6 +76,7 @@ type invoiceUpdateCtx struct {
|
|||
expiry uint32
|
||||
currentHeight int32
|
||||
finalCltvRejectDelta int32
|
||||
customRecords hop.CustomRecordSet
|
||||
}
|
||||
|
||||
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
|
||||
|
@ -125,9 +128,10 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
|||
// Record HTLC in the invoice database.
|
||||
newHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{
|
||||
ctx.circuitKey: {
|
||||
Amt: ctx.amtPaid,
|
||||
Expiry: ctx.expiry,
|
||||
AcceptHeight: ctx.currentHeight,
|
||||
Amt: ctx.amtPaid,
|
||||
Expiry: ctx.expiry,
|
||||
AcceptHeight: ctx.currentHeight,
|
||||
CustomRecords: ctx.customRecords,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -75,13 +75,14 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||
}
|
||||
|
||||
rpcHtlc := lnrpc.InvoiceHTLC{
|
||||
ChanId: key.ChanID.ToUint64(),
|
||||
HtlcIndex: key.HtlcID,
|
||||
AcceptHeight: int32(htlc.AcceptHeight),
|
||||
AcceptTime: htlc.AcceptTime.Unix(),
|
||||
ExpiryHeight: int32(htlc.Expiry),
|
||||
AmtMsat: uint64(htlc.Amt),
|
||||
State: state,
|
||||
ChanId: key.ChanID.ToUint64(),
|
||||
HtlcIndex: key.HtlcID,
|
||||
AcceptHeight: int32(htlc.AcceptHeight),
|
||||
AcceptTime: htlc.AcceptTime.Unix(),
|
||||
ExpiryHeight: int32(htlc.Expiry),
|
||||
AmtMsat: uint64(htlc.Amt),
|
||||
State: state,
|
||||
CustomRecords: htlc.CustomRecords,
|
||||
}
|
||||
|
||||
// Only report resolved times if htlc is resolved.
|
||||
|
|
1127
lnrpc/rpc.pb.go
1127
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load diff
|
@ -2396,6 +2396,9 @@ message InvoiceHTLC {
|
|||
|
||||
/// Current state the htlc is in.
|
||||
InvoiceHTLCState state = 8 [json_name = "state"];
|
||||
|
||||
/// Custom tlv records.
|
||||
map<uint64, bytes> custom_records = 9 [json_name = "custom_records"];
|
||||
}
|
||||
|
||||
message AddInvoiceResponse {
|
||||
|
|
|
@ -2819,6 +2819,14 @@
|
|||
"state": {
|
||||
"$ref": "#/definitions/lnrpcInvoiceHTLCState",
|
||||
"description": "/ Current state the htlc is in."
|
||||
},
|
||||
"custom_records": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"format": "byte"
|
||||
},
|
||||
"description": "/ Custom tlv records."
|
||||
}
|
||||
},
|
||||
"title": "/ Details of an HTLC that paid to an invoice"
|
||||
|
|
Loading…
Add table
Reference in a new issue