Merge pull request #3390 from joostjager/invoice-circuits
channeldb+cnct+invoices: track invoice htlcs
This commit is contained in:
26 files changed
+1993
-1010
No files matched your search
+2
-2
@@ -108,7 +108,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case uint64:
|
case int64, uint64:
|
||||||
if err := binary.Write(w, byteOrder, e); err != nil {
|
if err := binary.Write(w, byteOrder, e); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case *uint64:
|
case *int64, *uint64:
|
||||||
if err := binary.Read(r, byteOrder, e); err != nil {
|
if err := binary.Read(r, byteOrder, e); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ var (
|
|||||||
number: 10,
|
number: 10,
|
||||||
migration: migrateRouteSerialization,
|
migration: migrateRouteSerialization,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Add invoice htlc and cltv delta fields.
|
||||||
|
number: 11,
|
||||||
|
migration: migrateInvoices,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Big endian is the preferred byte order, due to cursor scans over
|
// Big endian is the preferred byte order, due to cursor scans over
|
||||||
@@ -124,6 +129,7 @@ type DB struct {
|
|||||||
*bbolt.DB
|
*bbolt.DB
|
||||||
dbPath string
|
dbPath string
|
||||||
graph *ChannelGraph
|
graph *ChannelGraph
|
||||||
|
now func() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens an existing channeldb. Any necessary schemas migrations due to
|
// Open opens an existing channeldb. Any necessary schemas migrations due to
|
||||||
@@ -157,6 +163,7 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) {
|
|||||||
chanDB := &DB{
|
chanDB := &DB{
|
||||||
DB: bdb,
|
DB: bdb,
|
||||||
dbPath: dbPath,
|
dbPath: dbPath,
|
||||||
|
now: time.Now,
|
||||||
}
|
}
|
||||||
chanDB.graph = newChannelGraph(
|
chanDB.graph = newChannelGraph(
|
||||||
chanDB, opts.RejectCacheSize, opts.ChannelCacheSize,
|
chanDB, opts.RejectCacheSize, opts.ChannelCacheSize,
|
||||||
|
|||||||
+40
-16
@@ -24,6 +24,8 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
|
|||||||
PaymentPreimage: pre,
|
PaymentPreimage: pre,
|
||||||
Value: value,
|
Value: value,
|
||||||
},
|
},
|
||||||
|
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||||
|
Expiry: 4000,
|
||||||
}
|
}
|
||||||
i.Memo = []byte("memo")
|
i.Memo = []byte("memo")
|
||||||
i.Receipt = []byte("receipt")
|
i.Receipt = []byte("receipt")
|
||||||
@@ -59,6 +61,7 @@ func TestInvoiceWorkflow(t *testing.T) {
|
|||||||
// Use single second precision to avoid false positive test
|
// Use single second precision to avoid false positive test
|
||||||
// failures due to the monotonic time component.
|
// failures due to the monotonic time component.
|
||||||
CreationDate: time.Unix(time.Now().Unix(), 0),
|
CreationDate: time.Unix(time.Now().Unix(), 0),
|
||||||
|
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||||
}
|
}
|
||||||
fakeInvoice.Memo = []byte("memo")
|
fakeInvoice.Memo = []byte("memo")
|
||||||
fakeInvoice.Receipt = []byte("receipt")
|
fakeInvoice.Receipt = []byte("receipt")
|
||||||
@@ -99,9 +102,7 @@ func TestInvoiceWorkflow(t *testing.T) {
|
|||||||
// now have the settled bit toggle to true and a non-default
|
// now have the settled bit toggle to true and a non-default
|
||||||
// SettledDate
|
// SettledDate
|
||||||
payAmt := fakeInvoice.Terms.Value * 2
|
payAmt := fakeInvoice.Terms.Value * 2
|
||||||
_, err = db.AcceptOrSettleInvoice(
|
_, err = db.UpdateInvoice(paymentHash, getUpdateInvoice(payAmt))
|
||||||
paymentHash, payAmt, checkHtlcParameters,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to settle invoice: %v", err)
|
t.Fatalf("unable to settle invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -264,8 +265,8 @@ func TestInvoiceAddTimeSeries(t *testing.T) {
|
|||||||
|
|
||||||
paymentHash := invoice.Terms.PaymentPreimage.Hash()
|
paymentHash := invoice.Terms.PaymentPreimage.Hash()
|
||||||
|
|
||||||
_, err := db.AcceptOrSettleInvoice(
|
_, err := db.UpdateInvoice(
|
||||||
paymentHash, 0, checkHtlcParameters,
|
paymentHash, getUpdateInvoice(0),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to settle invoice: %v", err)
|
t.Fatalf("unable to settle invoice: %v", err)
|
||||||
@@ -332,6 +333,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to make test db: %v", err)
|
t.Fatalf("unable to make test db: %v", err)
|
||||||
}
|
}
|
||||||
|
db.now = func() time.Time { return time.Unix(1, 0) }
|
||||||
|
|
||||||
// We'll start out by creating an invoice and writing it to the DB.
|
// We'll start out by creating an invoice and writing it to the DB.
|
||||||
amt := lnwire.NewMSatFromSatoshis(1000)
|
amt := lnwire.NewMSatFromSatoshis(1000)
|
||||||
@@ -347,8 +349,8 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// With the invoice in the DB, we'll now attempt to settle the invoice.
|
// With the invoice in the DB, we'll now attempt to settle the invoice.
|
||||||
dbInvoice, err := db.AcceptOrSettleInvoice(
|
dbInvoice, err := db.UpdateInvoice(
|
||||||
payHash, amt, checkHtlcParameters,
|
payHash, getUpdateInvoice(amt),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to settle invoice: %v", err)
|
t.Fatalf("unable to settle invoice: %v", err)
|
||||||
@@ -360,6 +362,14 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
|||||||
invoice.Terms.State = ContractSettled
|
invoice.Terms.State = ContractSettled
|
||||||
invoice.AmtPaid = amt
|
invoice.AmtPaid = amt
|
||||||
invoice.SettleDate = dbInvoice.SettleDate
|
invoice.SettleDate = dbInvoice.SettleDate
|
||||||
|
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||||
|
{}: {
|
||||||
|
Amt: amt,
|
||||||
|
AcceptTime: time.Unix(1, 0),
|
||||||
|
ResolveTime: time.Unix(1, 0),
|
||||||
|
State: HtlcStateSettled,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// We should get back the exact same invoice that we just inserted.
|
// We should get back the exact same invoice that we just inserted.
|
||||||
if !reflect.DeepEqual(dbInvoice, invoice) {
|
if !reflect.DeepEqual(dbInvoice, invoice) {
|
||||||
@@ -369,8 +379,8 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
|||||||
|
|
||||||
// If we try to settle the invoice again, then we should get the very
|
// If we try to settle the invoice again, then we should get the very
|
||||||
// same invoice back, but with an error this time.
|
// same invoice back, but with an error this time.
|
||||||
dbInvoice, err = db.AcceptOrSettleInvoice(
|
dbInvoice, err = db.UpdateInvoice(
|
||||||
payHash, amt, checkHtlcParameters,
|
payHash, getUpdateInvoice(amt),
|
||||||
)
|
)
|
||||||
if err != ErrInvoiceAlreadySettled {
|
if err != ErrInvoiceAlreadySettled {
|
||||||
t.Fatalf("expected ErrInvoiceAlreadySettled")
|
t.Fatalf("expected ErrInvoiceAlreadySettled")
|
||||||
@@ -416,8 +426,8 @@ func TestQueryInvoices(t *testing.T) {
|
|||||||
|
|
||||||
// We'll only settle half of all invoices created.
|
// We'll only settle half of all invoices created.
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
_, err := db.AcceptOrSettleInvoice(
|
_, err := db.UpdateInvoice(
|
||||||
paymentHash, i, checkHtlcParameters,
|
paymentHash, getUpdateInvoice(i),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to settle invoice: %v", err)
|
t.Fatalf("unable to settle invoice: %v", err)
|
||||||
@@ -661,10 +671,24 @@ func TestQueryInvoices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHtlcParameters(invoice *Invoice) error {
|
// getUpdateInvoice returns an invoice update callback that, when called,
|
||||||
if invoice.Terms.State == ContractSettled {
|
// settles the invoice with the given amount.
|
||||||
return ErrInvoiceAlreadySettled
|
func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
|
||||||
}
|
return func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||||
|
if invoice.Terms.State == ContractSettled {
|
||||||
|
return nil, ErrInvoiceAlreadySettled
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
update := &InvoiceUpdateDesc{
|
||||||
|
Preimage: invoice.Terms.PaymentPreimage,
|
||||||
|
State: ContractSettled,
|
||||||
|
Htlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||||
|
{}: {
|
||||||
|
Amt: amt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+366
-174
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/tlv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -92,6 +93,17 @@ const (
|
|||||||
// TODO(halseth): determine the max length payment request when field
|
// TODO(halseth): determine the max length payment request when field
|
||||||
// lengths are final.
|
// lengths are final.
|
||||||
MaxPaymentRequestSize = 4096
|
MaxPaymentRequestSize = 4096
|
||||||
|
|
||||||
|
// A set of tlv type definitions used to serialize invoice htlcs to the
|
||||||
|
// database.
|
||||||
|
chanIDType tlv.Type = 1
|
||||||
|
htlcIDType tlv.Type = 3
|
||||||
|
amtType tlv.Type = 5
|
||||||
|
acceptHeightType tlv.Type = 7
|
||||||
|
acceptTimeType tlv.Type = 9
|
||||||
|
resolveTimeType tlv.Type = 11
|
||||||
|
expiryHeightType tlv.Type = 13
|
||||||
|
stateType tlv.Type = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContractState describes the state the invoice is in.
|
// ContractState describes the state the invoice is in.
|
||||||
@@ -172,6 +184,13 @@ type Invoice struct {
|
|||||||
// for this invoice can be stored.
|
// for this invoice can be stored.
|
||||||
PaymentRequest []byte
|
PaymentRequest []byte
|
||||||
|
|
||||||
|
// FinalCltvDelta is the minimum required number of blocks before htlc
|
||||||
|
// expiry when the invoice is accepted.
|
||||||
|
FinalCltvDelta int32
|
||||||
|
|
||||||
|
// Expiry defines how long after creation this invoice should expire.
|
||||||
|
Expiry time.Duration
|
||||||
|
|
||||||
// CreationDate is the exact time the invoice was created.
|
// CreationDate is the exact time the invoice was created.
|
||||||
CreationDate time.Time
|
CreationDate time.Time
|
||||||
|
|
||||||
@@ -209,8 +228,85 @@ type Invoice struct {
|
|||||||
// that the invoice originally didn't specify an amount, or the sender
|
// that the invoice originally didn't specify an amount, or the sender
|
||||||
// overpaid.
|
// overpaid.
|
||||||
AmtPaid lnwire.MilliSatoshi
|
AmtPaid lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// Htlcs records all htlcs that paid to this invoice. Some of these
|
||||||
|
// htlcs may have been marked as cancelled.
|
||||||
|
Htlcs map[CircuitKey]*InvoiceHTLC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HtlcState defines the states an htlc paying to an invoice can be in.
|
||||||
|
type HtlcState uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HtlcStateAccepted indicates the htlc is locked-in, but not resolved.
|
||||||
|
HtlcStateAccepted HtlcState = iota
|
||||||
|
|
||||||
|
// HtlcStateCancelled indicates the htlc is cancelled back to the
|
||||||
|
// sender.
|
||||||
|
HtlcStateCancelled
|
||||||
|
|
||||||
|
// HtlcStateSettled indicates the htlc is settled.
|
||||||
|
HtlcStateSettled
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvoiceHTLC contains details about an htlc paying to this invoice.
|
||||||
|
type InvoiceHTLC struct {
|
||||||
|
// Amt is the amount that is carried by this htlc.
|
||||||
|
Amt lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// AcceptHeight is the block height at which the invoice registry
|
||||||
|
// decided to accept this htlc as a payment to the invoice. At this
|
||||||
|
// height, the invoice cltv delay must have been met.
|
||||||
|
AcceptHeight uint32
|
||||||
|
|
||||||
|
// AcceptTime is the wall clock time at which the invoice registry
|
||||||
|
// decided to accept the htlc.
|
||||||
|
AcceptTime time.Time
|
||||||
|
|
||||||
|
// ResolveTime is the wall clock time at which the invoice registry
|
||||||
|
// decided to settle the htlc.
|
||||||
|
ResolveTime time.Time
|
||||||
|
|
||||||
|
// Expiry is the expiry height of this htlc.
|
||||||
|
Expiry uint32
|
||||||
|
|
||||||
|
// State indicates the state the invoice htlc is currently in. A
|
||||||
|
// cancelled htlc isn't just removed from the invoice htlcs map, because
|
||||||
|
// we need AcceptedHeight to properly cancel the htlc back.
|
||||||
|
State HtlcState
|
||||||
|
}
|
||||||
|
|
||||||
|
// HtlcAcceptDesc describes the details of a newly accepted htlc.
|
||||||
|
type HtlcAcceptDesc struct {
|
||||||
|
// AcceptHeight is the block height at which this htlc was accepted.
|
||||||
|
AcceptHeight int32
|
||||||
|
|
||||||
|
// Amt is the amount that is carried by this htlc.
|
||||||
|
Amt lnwire.MilliSatoshi
|
||||||
|
|
||||||
|
// Expiry is the expiry height of this htlc.
|
||||||
|
Expiry uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvoiceUpdateDesc describes the changes that should be applied to the
|
||||||
|
// invoice.
|
||||||
|
type InvoiceUpdateDesc struct {
|
||||||
|
// State is the new state that this invoice should progress to.
|
||||||
|
State ContractState
|
||||||
|
|
||||||
|
// Htlcs describes the changes that need to be made to the invoice htlcs
|
||||||
|
// in the database. Htlc map entries with their value set should be
|
||||||
|
// added. If the map value is nil, the htlc should be cancelled.
|
||||||
|
Htlcs map[CircuitKey]*HtlcAcceptDesc
|
||||||
|
|
||||||
|
// Preimage must be set to the preimage when state is settled.
|
||||||
|
Preimage lntypes.Preimage
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvoiceUpdateCallback is a callback used in the db transaction to update the
|
||||||
|
// invoice.
|
||||||
|
type InvoiceUpdateCallback = func(invoice *Invoice) (*InvoiceUpdateDesc, error)
|
||||||
|
|
||||||
func validateInvoice(i *Invoice) error {
|
func validateInvoice(i *Invoice) error {
|
||||||
if len(i.Memo) > MaxMemoSize {
|
if len(i.Memo) > MaxMemoSize {
|
||||||
return fmt.Errorf("max length a memo is %v, and invoice "+
|
return fmt.Errorf("max length a memo is %v, and invoice "+
|
||||||
@@ -624,21 +720,17 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptOrSettleInvoice attempts to mark an invoice corresponding to the passed
|
// UpdateInvoice attempts to update an invoice corresponding to the passed
|
||||||
// payment hash as settled. If an invoice matching the passed payment hash
|
// payment hash. If an invoice matching the passed payment hash doesn't exist
|
||||||
// doesn't existing within the database, then the action will fail with a "not
|
// within the database, then the action will fail with a "not found" error.
|
||||||
// found" error.
|
|
||||||
//
|
//
|
||||||
// When the preimage for the invoice is unknown (hold invoice), the invoice is
|
// The update is performed inside the same database transaction that fetches the
|
||||||
// marked as accepted.
|
// invoice and is therefore atomic. The fields to update are controlled by the
|
||||||
//
|
// supplied callback.
|
||||||
// TODO: Store invoice cltv as separate field in database so that it doesn't
|
func (d *DB) UpdateInvoice(paymentHash lntypes.Hash,
|
||||||
// need to be decoded from the payment request.
|
callback InvoiceUpdateCallback) (*Invoice, error) {
|
||||||
func (d *DB) AcceptOrSettleInvoice(paymentHash [32]byte,
|
|
||||||
amtPaid lnwire.MilliSatoshi,
|
|
||||||
checkHtlcParameters func(invoice *Invoice) error) (*Invoice, error) {
|
|
||||||
|
|
||||||
var settledInvoice *Invoice
|
var updatedInvoice *Invoice
|
||||||
err := d.Update(func(tx *bbolt.Tx) error {
|
err := d.Update(func(tx *bbolt.Tx) error {
|
||||||
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -664,49 +756,9 @@ func (d *DB) AcceptOrSettleInvoice(paymentHash [32]byte,
|
|||||||
return ErrInvoiceNotFound
|
return ErrInvoiceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
settledInvoice, err = acceptOrSettleInvoice(
|
updatedInvoice, err = d.updateInvoice(
|
||||||
invoices, settleIndex, invoiceNum, amtPaid,
|
paymentHash, invoices, settleIndex, invoiceNum,
|
||||||
checkHtlcParameters,
|
callback,
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return settledInvoice, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettleHoldInvoice sets the preimage of a hodl invoice and marks the invoice
|
|
||||||
// as settled.
|
|
||||||
func (d *DB) SettleHoldInvoice(preimage lntypes.Preimage) (*Invoice, error) {
|
|
||||||
var updatedInvoice *Invoice
|
|
||||||
hash := preimage.Hash()
|
|
||||||
err := d.Update(func(tx *bbolt.Tx) error {
|
|
||||||
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
invoiceIndex, err := invoices.CreateBucketIfNotExists(
|
|
||||||
invoiceIndexBucket,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
settleIndex, err := invoices.CreateBucketIfNotExists(
|
|
||||||
settleIndexBucket,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the invoice index to see if an invoice paying to this
|
|
||||||
// hash exists within the DB.
|
|
||||||
invoiceNum := invoiceIndex.Get(hash[:])
|
|
||||||
if invoiceNum == nil {
|
|
||||||
return ErrInvoiceNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedInvoice, err = settleHoldInvoice(
|
|
||||||
invoices, settleIndex, invoiceNum, preimage,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -715,37 +767,6 @@ func (d *DB) SettleHoldInvoice(preimage lntypes.Preimage) (*Invoice, error) {
|
|||||||
return updatedInvoice, err
|
return updatedInvoice, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelInvoice attempts to cancel the invoice corresponding to the passed
|
|
||||||
// payment hash.
|
|
||||||
func (d *DB) CancelInvoice(paymentHash lntypes.Hash) (*Invoice, error) {
|
|
||||||
var canceledInvoice *Invoice
|
|
||||||
err := d.Update(func(tx *bbolt.Tx) error {
|
|
||||||
invoices, err := tx.CreateBucketIfNotExists(invoiceBucket)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
invoiceIndex, err := invoices.CreateBucketIfNotExists(
|
|
||||||
invoiceIndexBucket,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the invoice index to see if an invoice paying to this
|
|
||||||
// hash exists within the DB.
|
|
||||||
invoiceNum := invoiceIndex.Get(paymentHash[:])
|
|
||||||
if invoiceNum == nil {
|
|
||||||
return ErrInvoiceNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
canceledInvoice, err = cancelInvoice(invoices, invoiceNum)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
return canceledInvoice, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvoicesSettledSince can be used by callers to catch up any settled invoices
|
// InvoicesSettledSince can be used by callers to catch up any settled invoices
|
||||||
// they missed within the settled invoice time series. We'll return all known
|
// they missed within the settled invoice time series. We'll return all known
|
||||||
// settled invoice that have a settle index higher than the passed
|
// settled invoice that have a settle index higher than the passed
|
||||||
@@ -855,7 +876,7 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket,
|
|||||||
// Finally, serialize the invoice itself to be written to the disk.
|
// Finally, serialize the invoice itself to be written to the disk.
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := serializeInvoice(&buf, i); err != nil {
|
if err := serializeInvoice(&buf, i); err != nil {
|
||||||
return 0, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil {
|
if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil {
|
||||||
@@ -865,6 +886,11 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket,
|
|||||||
return nextAddSeqNo, nil
|
return nextAddSeqNo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serializeInvoice serializes an invoice to a writer.
|
||||||
|
//
|
||||||
|
// Note: this function is in use for a migration. Before making changes that
|
||||||
|
// would modify the on disk format, make a copy of the original code and store
|
||||||
|
// it with the migration.
|
||||||
func serializeInvoice(w io.Writer, i *Invoice) error {
|
func serializeInvoice(w io.Writer, i *Invoice) error {
|
||||||
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
|
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -876,6 +902,14 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := binary.Write(w, byteOrder, i.FinalCltvDelta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Write(w, byteOrder, int64(i.Expiry)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
birthBytes, err := i.CreationDate.MarshalBinary()
|
birthBytes, err := i.CreationDate.MarshalBinary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -918,6 +952,57 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := serializeHtlcs(w, i.Htlcs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to
|
||||||
|
// a writer.
|
||||||
|
func serializeHtlcs(w io.Writer, htlcs map[CircuitKey]*InvoiceHTLC) error {
|
||||||
|
for key, htlc := range htlcs {
|
||||||
|
// Encode the htlc in a tlv stream.
|
||||||
|
chanID := key.ChanID.ToUint64()
|
||||||
|
amt := uint64(htlc.Amt)
|
||||||
|
acceptTime := uint64(htlc.AcceptTime.UnixNano())
|
||||||
|
resolveTime := uint64(htlc.ResolveTime.UnixNano())
|
||||||
|
state := uint8(htlc.State)
|
||||||
|
|
||||||
|
tlvStream, err := tlv.NewStream(
|
||||||
|
tlv.MakePrimitiveRecord(chanIDType, &chanID),
|
||||||
|
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
|
||||||
|
tlv.MakePrimitiveRecord(amtType, &amt),
|
||||||
|
tlv.MakePrimitiveRecord(
|
||||||
|
acceptHeightType, &htlc.AcceptHeight,
|
||||||
|
),
|
||||||
|
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
|
||||||
|
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
|
||||||
|
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
|
||||||
|
tlv.MakePrimitiveRecord(stateType, &state),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := tlvStream.Encode(&b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the length of the tlv stream followed by the stream
|
||||||
|
// bytes.
|
||||||
|
err = binary.Write(w, byteOrder, uint64(b.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(b.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -951,6 +1036,16 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
|
|||||||
return invoice, err
|
return invoice, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(r, byteOrder, &invoice.FinalCltvDelta); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiry int64
|
||||||
|
if err := binary.Read(r, byteOrder, &expiry); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
invoice.Expiry = time.Duration(expiry)
|
||||||
|
|
||||||
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
|
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return invoice, err
|
return invoice, err
|
||||||
@@ -990,38 +1085,204 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
|
|||||||
return invoice, err
|
return invoice, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invoice.Htlcs, err = deserializeHtlcs(r)
|
||||||
|
if err != nil {
|
||||||
|
return Invoice{}, err
|
||||||
|
}
|
||||||
|
|
||||||
return invoice, nil
|
return invoice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket,
|
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
|
||||||
invoiceNum []byte, amtPaid lnwire.MilliSatoshi,
|
// as a map.
|
||||||
checkHtlcParameters func(invoice *Invoice) error) (
|
func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) {
|
||||||
*Invoice, error) {
|
htlcs := make(map[CircuitKey]*InvoiceHTLC, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read the length of the tlv stream for this htlc.
|
||||||
|
var streamLen uint64
|
||||||
|
if err := binary.Read(r, byteOrder, &streamLen); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
streamBytes := make([]byte, streamLen)
|
||||||
|
if _, err := r.Read(streamBytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
streamReader := bytes.NewReader(streamBytes)
|
||||||
|
|
||||||
|
// Decode the contents into the htlc fields.
|
||||||
|
var (
|
||||||
|
htlc InvoiceHTLC
|
||||||
|
key CircuitKey
|
||||||
|
chanID uint64
|
||||||
|
state uint8
|
||||||
|
acceptTime, resolveTime uint64
|
||||||
|
amt uint64
|
||||||
|
)
|
||||||
|
tlvStream, err := tlv.NewStream(
|
||||||
|
tlv.MakePrimitiveRecord(chanIDType, &chanID),
|
||||||
|
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
|
||||||
|
tlv.MakePrimitiveRecord(amtType, &amt),
|
||||||
|
tlv.MakePrimitiveRecord(
|
||||||
|
acceptHeightType, &htlc.AcceptHeight,
|
||||||
|
),
|
||||||
|
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
|
||||||
|
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
|
||||||
|
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
|
||||||
|
tlv.MakePrimitiveRecord(stateType, &state),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tlvStream.Decode(streamReader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key.ChanID = lnwire.NewShortChanIDFromInt(chanID)
|
||||||
|
htlc.AcceptTime = time.Unix(0, int64(acceptTime))
|
||||||
|
htlc.ResolveTime = time.Unix(0, int64(resolveTime))
|
||||||
|
htlc.State = HtlcState(state)
|
||||||
|
htlc.Amt = lnwire.MilliSatoshi(amt)
|
||||||
|
|
||||||
|
htlcs[key] = &htlc
|
||||||
|
}
|
||||||
|
|
||||||
|
return htlcs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copySlice allocates a new slice and copies the source into it.
|
||||||
|
func copySlice(src []byte) []byte {
|
||||||
|
dest := make([]byte, len(src))
|
||||||
|
copy(dest, src)
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyInvoice makes a deep copy of the supplied invoice.
|
||||||
|
func copyInvoice(src *Invoice) *Invoice {
|
||||||
|
dest := Invoice{
|
||||||
|
Memo: copySlice(src.Memo),
|
||||||
|
Receipt: copySlice(src.Receipt),
|
||||||
|
PaymentRequest: copySlice(src.PaymentRequest),
|
||||||
|
FinalCltvDelta: src.FinalCltvDelta,
|
||||||
|
CreationDate: src.CreationDate,
|
||||||
|
SettleDate: src.SettleDate,
|
||||||
|
Terms: src.Terms,
|
||||||
|
AddIndex: src.AddIndex,
|
||||||
|
SettleIndex: src.SettleIndex,
|
||||||
|
AmtPaid: src.AmtPaid,
|
||||||
|
Htlcs: make(
|
||||||
|
map[CircuitKey]*InvoiceHTLC, len(src.Htlcs),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range src.Htlcs {
|
||||||
|
dest.Htlcs[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateInvoice fetches the invoice, obtains the update descriptor from the
|
||||||
|
// callback and applies the updates in a single db transaction.
|
||||||
|
func (d *DB) updateInvoice(hash lntypes.Hash, invoices, settleIndex *bbolt.Bucket,
|
||||||
|
invoiceNum []byte, callback InvoiceUpdateCallback) (*Invoice, error) {
|
||||||
|
|
||||||
invoice, err := fetchInvoice(invoiceNum, invoices)
|
invoice, err := fetchInvoice(invoiceNum, invoices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the invoice is still open, check the htlc parameters.
|
preUpdateState := invoice.Terms.State
|
||||||
if err := checkHtlcParameters(&invoice); err != nil {
|
|
||||||
|
// Create deep copy to prevent any accidental modification in the
|
||||||
|
// callback.
|
||||||
|
copy := copyInvoice(&invoice)
|
||||||
|
|
||||||
|
// Call the callback and obtain the update descriptor.
|
||||||
|
update, err := callback(copy)
|
||||||
|
if err != nil {
|
||||||
return &invoice, err
|
return &invoice, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we can settle or this is an hold invoice and we need
|
// Update invoice state.
|
||||||
// to wait for the preimage.
|
invoice.Terms.State = update.State
|
||||||
holdInvoice := invoice.Terms.PaymentPreimage == UnknownPreimage
|
|
||||||
if holdInvoice {
|
now := d.now()
|
||||||
invoice.Terms.State = ContractAccepted
|
|
||||||
} else {
|
// Update htlc set.
|
||||||
err := setSettleFields(settleIndex, invoiceNum, &invoice)
|
for key, htlcUpdate := range update.Htlcs {
|
||||||
|
htlc, ok := invoice.Htlcs[key]
|
||||||
|
|
||||||
|
// No update means the htlc needs to be cancelled.
|
||||||
|
if htlcUpdate == nil {
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown htlc %v", key)
|
||||||
|
}
|
||||||
|
if htlc.State == HtlcStateSettled {
|
||||||
|
return nil, fmt.Errorf("cannot cancel a " +
|
||||||
|
"settled htlc")
|
||||||
|
}
|
||||||
|
|
||||||
|
htlc.State = HtlcStateCancelled
|
||||||
|
htlc.ResolveTime = now
|
||||||
|
invoice.AmtPaid -= htlc.Amt
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new htlc paying to the invoice.
|
||||||
|
if ok {
|
||||||
|
return nil, fmt.Errorf("htlc %v already exists", key)
|
||||||
|
}
|
||||||
|
htlc = &InvoiceHTLC{
|
||||||
|
Amt: htlcUpdate.Amt,
|
||||||
|
Expiry: htlcUpdate.Expiry,
|
||||||
|
AcceptHeight: uint32(htlcUpdate.AcceptHeight),
|
||||||
|
AcceptTime: now,
|
||||||
|
}
|
||||||
|
if preUpdateState == ContractSettled {
|
||||||
|
htlc.State = HtlcStateSettled
|
||||||
|
htlc.ResolveTime = now
|
||||||
|
} else {
|
||||||
|
htlc.State = HtlcStateAccepted
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.Htlcs[key] = htlc
|
||||||
|
invoice.AmtPaid += htlc.Amt
|
||||||
|
}
|
||||||
|
|
||||||
|
// If invoice moved to the settled state, update settle index and settle
|
||||||
|
// time.
|
||||||
|
if preUpdateState != invoice.Terms.State &&
|
||||||
|
invoice.Terms.State == ContractSettled {
|
||||||
|
|
||||||
|
if update.Preimage.Hash() != hash {
|
||||||
|
return nil, fmt.Errorf("preimage does not match")
|
||||||
|
}
|
||||||
|
invoice.Terms.PaymentPreimage = update.Preimage
|
||||||
|
|
||||||
|
// Settle all accepted htlcs.
|
||||||
|
for _, htlc := range invoice.Htlcs {
|
||||||
|
if htlc.State != HtlcStateAccepted {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
htlc.State = HtlcStateSettled
|
||||||
|
htlc.ResolveTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
err := setSettleFields(settleIndex, invoiceNum, &invoice, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice.AmtPaid = amtPaid
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := serializeInvoice(&buf, &invoice); err != nil {
|
if err := serializeInvoice(&buf, &invoice); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1035,7 +1296,7 @@ func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte,
|
func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte,
|
||||||
invoice *Invoice) error {
|
invoice *Invoice, now time.Time) error {
|
||||||
|
|
||||||
// Now that we know the invoice hasn't already been settled, we'll
|
// Now that we know the invoice hasn't already been settled, we'll
|
||||||
// update the settle index so we can place this settle event in the
|
// update the settle index so we can place this settle event in the
|
||||||
@@ -1052,77 +1313,8 @@ func setSettleFields(settleIndex *bbolt.Bucket, invoiceNum []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
invoice.Terms.State = ContractSettled
|
invoice.Terms.State = ContractSettled
|
||||||
invoice.SettleDate = time.Now()
|
invoice.SettleDate = now
|
||||||
invoice.SettleIndex = nextSettleSeqNo
|
invoice.SettleIndex = nextSettleSeqNo
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func settleHoldInvoice(invoices, settleIndex *bbolt.Bucket,
|
|
||||||
invoiceNum []byte, preimage lntypes.Preimage) (*Invoice,
|
|
||||||
error) {
|
|
||||||
|
|
||||||
invoice, err := fetchInvoice(invoiceNum, invoices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch invoice.Terms.State {
|
|
||||||
case ContractOpen:
|
|
||||||
return &invoice, ErrInvoiceStillOpen
|
|
||||||
case ContractCanceled:
|
|
||||||
return &invoice, ErrInvoiceAlreadyCanceled
|
|
||||||
case ContractSettled:
|
|
||||||
return &invoice, ErrInvoiceAlreadySettled
|
|
||||||
}
|
|
||||||
|
|
||||||
invoice.Terms.PaymentPreimage = preimage
|
|
||||||
|
|
||||||
err = setSettleFields(settleIndex, invoiceNum, &invoice)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := serializeInvoice(&buf, &invoice); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &invoice, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancelInvoice(invoices *bbolt.Bucket, invoiceNum []byte) (
|
|
||||||
*Invoice, error) {
|
|
||||||
|
|
||||||
invoice, err := fetchInvoice(invoiceNum, invoices)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch invoice.Terms.State {
|
|
||||||
case ContractSettled:
|
|
||||||
return &invoice, ErrInvoiceAlreadySettled
|
|
||||||
case ContractCanceled:
|
|
||||||
return &invoice, ErrInvoiceAlreadyCanceled
|
|
||||||
}
|
|
||||||
|
|
||||||
invoice.Terms.State = ContractCanceled
|
|
||||||
|
|
||||||
// Set AmtPaid back to 0, in case the invoice was already accepted.
|
|
||||||
invoice.AmtPaid = 0
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := serializeInvoice(&buf, &invoice); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &invoice, nil
|
|
||||||
}
|
|
||||||
@@ -177,7 +177,7 @@ func fetchPaymentStatusTx(tx *bbolt.Tx, paymentHash [32]byte) (PaymentStatus, er
|
|||||||
func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error {
|
func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error {
|
||||||
var scratch [8]byte
|
var scratch [8]byte
|
||||||
|
|
||||||
if err := serializeInvoice(w, &p.Invoice); err != nil {
|
if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ func deserializeOutgoingPayment(r io.Reader) (*outgoingPayment, error) {
|
|||||||
|
|
||||||
p := &outgoingPayment{}
|
p := &outgoingPayment{}
|
||||||
|
|
||||||
inv, err := deserializeInvoice(r)
|
inv, err := deserializeInvoiceLegacy(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
package channeldb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/coreos/bbolt"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
|
litecoinCfg "github.com/ltcsuite/ltcd/chaincfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// migrateInvoices adds invoice htlcs and a separate cltv delta field to the
|
||||||
|
// invoices.
|
||||||
|
func migrateInvoices(tx *bbolt.Tx) error {
|
||||||
|
log.Infof("Migrating invoices to new invoice format")
|
||||||
|
|
||||||
|
invoiceB := tx.Bucket(invoiceBucket)
|
||||||
|
if invoiceB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through the entire key space of the top-level invoice bucket.
|
||||||
|
// If key with a non-nil value stores the next invoice ID which maps to
|
||||||
|
// the corresponding invoice. Store those keys first, because it isn't
|
||||||
|
// safe to modify the bucket inside a ForEach loop.
|
||||||
|
var invoiceKeys [][]byte
|
||||||
|
err := invoiceB.ForEach(func(k, v []byte) error {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceKeys = append(invoiceKeys, k)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nets := []*bitcoinCfg.Params{
|
||||||
|
&bitcoinCfg.MainNetParams, &bitcoinCfg.SimNetParams,
|
||||||
|
&bitcoinCfg.RegressionNetParams, &bitcoinCfg.TestNet3Params,
|
||||||
|
}
|
||||||
|
|
||||||
|
ltcNets := []*litecoinCfg.Params{
|
||||||
|
&litecoinCfg.MainNetParams, &litecoinCfg.SimNetParams,
|
||||||
|
&litecoinCfg.RegressionNetParams, &litecoinCfg.TestNet4Params,
|
||||||
|
}
|
||||||
|
for _, net := range ltcNets {
|
||||||
|
var convertedNet bitcoinCfg.Params
|
||||||
|
convertedNet.Bech32HRPSegwit = net.Bech32HRPSegwit
|
||||||
|
nets = append(nets, &convertedNet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all stored keys and migrate the invoices.
|
||||||
|
for _, k := range invoiceKeys {
|
||||||
|
v := invoiceB.Get(k)
|
||||||
|
|
||||||
|
// Deserialize the invoice with the deserializing function that
|
||||||
|
// was in use for this version of the database.
|
||||||
|
invoiceReader := bytes.NewReader(v)
|
||||||
|
invoice, err := deserializeInvoiceLegacy(invoiceReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to decode the payment request for every possible net to
|
||||||
|
// avoid passing a the active network to channeldb. This would
|
||||||
|
// be a layering violation, while this migration is only running
|
||||||
|
// once and will likely be removed in the future.
|
||||||
|
var payReq *zpay32.Invoice
|
||||||
|
for _, net := range nets {
|
||||||
|
payReq, err = zpay32.Decode(
|
||||||
|
string(invoice.PaymentRequest), net,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if payReq == nil {
|
||||||
|
return fmt.Errorf("cannot decode payreq")
|
||||||
|
}
|
||||||
|
invoice.FinalCltvDelta = int32(payReq.MinFinalCLTVExpiry())
|
||||||
|
invoice.Expiry = payReq.Expiry()
|
||||||
|
|
||||||
|
// Serialize the invoice in the new format and use it to replace
|
||||||
|
// the old invoice in the database.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := serializeInvoice(&buf, &invoice); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = invoiceB.Put(k, buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Migration of invoices completed!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeInvoiceLegacy(r io.Reader) (Invoice, error) {
|
||||||
|
var err error
|
||||||
|
invoice := Invoice{}
|
||||||
|
|
||||||
|
// TODO(roasbeef): use read full everywhere
|
||||||
|
invoice.Memo, err = wire.ReadVarBytes(r, 0, MaxMemoSize, "")
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
invoice.Receipt, err = wire.ReadVarBytes(r, 0, MaxReceiptSize, "")
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "")
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
if err := invoice.CreationDate.UnmarshalBinary(birthBytes); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
settledBytes, err := wire.ReadVarBytes(r, 0, 300, "settled")
|
||||||
|
if err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
if err := invoice.SettleDate.UnmarshalBinary(settledBytes); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.ReadFull(r, invoice.Terms.PaymentPreimage[:]); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
var scratch [8]byte
|
||||||
|
if _, err := io.ReadFull(r, scratch[:]); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
|
||||||
|
|
||||||
|
if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(r, byteOrder, &invoice.AddIndex); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, byteOrder, &invoice.SettleIndex); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, byteOrder, &invoice.AmtPaid); err != nil {
|
||||||
|
return invoice, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serializeInvoiceLegacy serializes an invoice in the format of the previous db
|
||||||
|
// version.
|
||||||
|
func serializeInvoiceLegacy(w io.Writer, i *Invoice) error {
|
||||||
|
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
birthBytes, err := i.CreationDate.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wire.WriteVarBytes(w, 0, birthBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
settleBytes, err := i.SettleDate.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wire.WriteVarBytes(w, 0, settleBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(i.Terms.PaymentPreimage[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var scratch [8]byte
|
||||||
|
byteOrder.PutUint64(scratch[:], uint64(i.Terms.Value))
|
||||||
|
if _, err := w.Write(scratch[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Write(w, byteOrder, i.Terms.State); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Write(w, byteOrder, i.AddIndex); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, byteOrder, i.SettleIndex); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := binary.Write(w, byteOrder, int64(i.AmtPaid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package channeldb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/coreos/bbolt"
|
||||||
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
|
litecoinCfg "github.com/ltcsuite/ltcd/chaincfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testPrivKeyBytes = []byte{
|
||||||
|
0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
|
||||||
|
0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
|
||||||
|
0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
|
||||||
|
0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCltvDelta = int32(50)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMigrateInvoices checks that invoices are migrated correctly.
|
||||||
|
func TestMigrateInvoices(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
payReqBtc, err := getPayReq(&bitcoinCfg.MainNetParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ltcNetParams bitcoinCfg.Params
|
||||||
|
ltcNetParams.Bech32HRPSegwit = litecoinCfg.MainNetParams.Bech32HRPSegwit
|
||||||
|
payReqLtc, err := getPayReq(<cNetParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoices := []Invoice{
|
||||||
|
{
|
||||||
|
PaymentRequest: []byte(payReqBtc),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PaymentRequest: []byte(payReqLtc),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeMigrationFunc := func(d *DB) {
|
||||||
|
err := d.Update(func(tx *bbolt.Tx) error {
|
||||||
|
invoicesBucket, err := tx.CreateBucketIfNotExists(
|
||||||
|
invoiceBucket,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceNum := uint32(1)
|
||||||
|
for _, invoice := range invoices {
|
||||||
|
var invoiceKey [4]byte
|
||||||
|
byteOrder.PutUint32(invoiceKey[:], invoiceNum)
|
||||||
|
invoiceNum++
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := serializeInvoiceLegacy(&buf, &invoice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = invoicesBucket.Put(
|
||||||
|
invoiceKey[:], buf.Bytes(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that all invoices were migrated.
|
||||||
|
afterMigrationFunc := func(d *DB) {
|
||||||
|
meta, err := d.FetchMeta(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.DbVersionNumber != 1 {
|
||||||
|
t.Fatal("migration 'invoices' wasn't applied")
|
||||||
|
}
|
||||||
|
|
||||||
|
dbInvoices, err := d.FetchAllInvoices(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to fetch invoices: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(invoices) != len(dbInvoices) {
|
||||||
|
t.Fatalf("expected %d invoices, got %d", len(invoices),
|
||||||
|
len(dbInvoices))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dbInvoice := range dbInvoices {
|
||||||
|
if dbInvoice.FinalCltvDelta != testCltvDelta {
|
||||||
|
t.Fatal("incorrect final cltv delta")
|
||||||
|
}
|
||||||
|
if dbInvoice.Expiry != 3600*time.Second {
|
||||||
|
t.Fatal("incorrect expiry")
|
||||||
|
}
|
||||||
|
if len(dbInvoice.Htlcs) != 0 {
|
||||||
|
t.Fatal("expected no htlcs after migration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyMigration(t,
|
||||||
|
beforeMigrationFunc,
|
||||||
|
afterMigrationFunc,
|
||||||
|
migrateInvoices,
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signDigestCompact generates a test signature to be used in the generation of
|
||||||
|
// test payment requests.
|
||||||
|
func signDigestCompact(hash []byte) ([]byte, error) {
|
||||||
|
// Should the signature reference a compressed public key or not.
|
||||||
|
isCompressedKey := true
|
||||||
|
|
||||||
|
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes)
|
||||||
|
|
||||||
|
// btcec.SignCompact returns a pubkey-recoverable signature
|
||||||
|
sig, err := btcec.SignCompact(
|
||||||
|
btcec.S256(), privKey, hash, isCompressedKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't sign the hash: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPayReq creates a payment request for the given net.
|
||||||
|
func getPayReq(net *bitcoinCfg.Params) (string, error) {
|
||||||
|
options := []func(*zpay32.Invoice){
|
||||||
|
zpay32.CLTVExpiry(uint64(testCltvDelta)),
|
||||||
|
zpay32.Description("test"),
|
||||||
|
}
|
||||||
|
|
||||||
|
payReq, err := zpay32.NewInvoice(
|
||||||
|
net, [32]byte{}, time.Unix(1, 0), options...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return payReq.Encode(
|
||||||
|
zpay32.MessageSigner{
|
||||||
|
SignCompact: signDigestCompact,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -168,7 +168,7 @@ func migrateInvoiceTimeSeries(tx *bbolt.Tx) error {
|
|||||||
invoiceBytesCopy = append(invoiceBytesCopy, padding...)
|
invoiceBytesCopy = append(invoiceBytesCopy, padding...)
|
||||||
|
|
||||||
invoiceReader := bytes.NewReader(invoiceBytesCopy)
|
invoiceReader := bytes.NewReader(invoiceBytesCopy)
|
||||||
invoice, err := deserializeInvoice(invoiceReader)
|
invoice, err := deserializeInvoiceLegacy(invoiceReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to decode invoice: %v", err)
|
return fmt.Errorf("unable to decode invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ func migrateInvoiceTimeSeries(tx *bbolt.Tx) error {
|
|||||||
// We've fully migrated an invoice, so we'll now update the
|
// We've fully migrated an invoice, so we'll now update the
|
||||||
// invoice in-place.
|
// invoice in-place.
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := serializeInvoice(&b, &invoice); err != nil {
|
if err := serializeInvoiceLegacy(&b, &invoice); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ func (c *ChannelArbitrator) relaunchResolvers() error {
|
|||||||
"resolvers", c.cfg.ChanPoint, len(unresolvedContracts))
|
"resolvers", c.cfg.ChanPoint, len(unresolvedContracts))
|
||||||
|
|
||||||
for _, resolver := range unresolvedContracts {
|
for _, resolver := range unresolvedContracts {
|
||||||
supplementResolver(resolver, htlcMap)
|
c.supplementResolver(resolver, htlcMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.launchResolvers(unresolvedContracts)
|
c.launchResolvers(unresolvedContracts)
|
||||||
@@ -489,24 +489,22 @@ func (c *ChannelArbitrator) relaunchResolvers() error {
|
|||||||
|
|
||||||
// supplementResolver takes a resolver as it is restored from the log and fills
|
// supplementResolver takes a resolver as it is restored from the log and fills
|
||||||
// in missing data from the htlcMap.
|
// in missing data from the htlcMap.
|
||||||
func supplementResolver(resolver ContractResolver,
|
func (c *ChannelArbitrator) supplementResolver(resolver ContractResolver,
|
||||||
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
||||||
|
|
||||||
switch r := resolver.(type) {
|
switch r := resolver.(type) {
|
||||||
|
|
||||||
case *htlcSuccessResolver:
|
case *htlcSuccessResolver:
|
||||||
return supplementSuccessResolver(r, htlcMap)
|
return c.supplementSuccessResolver(r, htlcMap)
|
||||||
|
|
||||||
case *htlcIncomingContestResolver:
|
case *htlcIncomingContestResolver:
|
||||||
return supplementSuccessResolver(
|
return c.supplementIncomingContestResolver(r, htlcMap)
|
||||||
&r.htlcSuccessResolver, htlcMap,
|
|
||||||
)
|
|
||||||
|
|
||||||
case *htlcTimeoutResolver:
|
case *htlcTimeoutResolver:
|
||||||
return supplementTimeoutResolver(r, htlcMap)
|
return c.supplementTimeoutResolver(r, htlcMap)
|
||||||
|
|
||||||
case *htlcOutgoingContestResolver:
|
case *htlcOutgoingContestResolver:
|
||||||
return supplementTimeoutResolver(
|
return c.supplementTimeoutResolver(
|
||||||
&r.htlcTimeoutResolver, htlcMap,
|
&r.htlcTimeoutResolver, htlcMap,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -514,9 +512,33 @@ func supplementResolver(resolver ContractResolver,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// supplementSuccessResolver takes a htlcIncomingContestResolver as it is
|
||||||
|
// restored from the log and fills in missing data from the htlcMap.
|
||||||
|
func (c *ChannelArbitrator) supplementIncomingContestResolver(
|
||||||
|
r *htlcIncomingContestResolver,
|
||||||
|
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
||||||
|
|
||||||
|
res := r.htlcResolution
|
||||||
|
htlcPoint := res.HtlcPoint()
|
||||||
|
htlc, ok := htlcMap[htlcPoint]
|
||||||
|
if !ok {
|
||||||
|
return errors.New(
|
||||||
|
"htlc for incoming contest resolver unavailable",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.htlcAmt = htlc.Amt
|
||||||
|
r.circuitKey = channeldb.CircuitKey{
|
||||||
|
ChanID: c.cfg.ShortChanID,
|
||||||
|
HtlcID: htlc.HtlcIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// supplementSuccessResolver takes a htlcSuccessResolver as it is restored from
|
// supplementSuccessResolver takes a htlcSuccessResolver as it is restored from
|
||||||
// the log and fills in missing data from the htlcMap.
|
// the log and fills in missing data from the htlcMap.
|
||||||
func supplementSuccessResolver(r *htlcSuccessResolver,
|
func (c *ChannelArbitrator) supplementSuccessResolver(r *htlcSuccessResolver,
|
||||||
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
||||||
|
|
||||||
res := r.htlcResolution
|
res := r.htlcResolution
|
||||||
@@ -533,7 +555,7 @@ func supplementSuccessResolver(r *htlcSuccessResolver,
|
|||||||
|
|
||||||
// supplementTimeoutResolver takes a htlcSuccessResolver as it is restored from
|
// supplementTimeoutResolver takes a htlcSuccessResolver as it is restored from
|
||||||
// the log and fills in missing data from the htlcMap.
|
// the log and fills in missing data from the htlcMap.
|
||||||
func supplementTimeoutResolver(r *htlcTimeoutResolver,
|
func (c *ChannelArbitrator) supplementTimeoutResolver(r *htlcTimeoutResolver,
|
||||||
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
htlcMap map[wire.OutPoint]*channeldb.HTLC) error {
|
||||||
|
|
||||||
res := r.htlcResolution
|
res := r.htlcResolution
|
||||||
@@ -1326,7 +1348,7 @@ func (c *ChannelArbitrator) isPreimageAvailable(hash lntypes.Hash) (bool,
|
|||||||
// than the invoice cltv delta. We don't want to go to chain only to
|
// than the invoice cltv delta. We don't want to go to chain only to
|
||||||
// have the incoming contest resolver decide that we don't want to
|
// have the incoming contest resolver decide that we don't want to
|
||||||
// settle this invoice.
|
// settle this invoice.
|
||||||
invoice, _, err := c.cfg.Registry.LookupInvoice(hash)
|
invoice, err := c.cfg.Registry.LookupInvoice(hash)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
case channeldb.ErrInvoiceNotFound, channeldb.ErrNoInvoicesCreated:
|
case channeldb.ErrInvoiceNotFound, channeldb.ErrNoInvoicesCreated:
|
||||||
@@ -1723,9 +1745,15 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
circuitKey := channeldb.CircuitKey{
|
||||||
|
HtlcID: htlc.HtlcIndex,
|
||||||
|
ChanID: c.cfg.ShortChanID,
|
||||||
|
}
|
||||||
|
|
||||||
resKit.Quit = make(chan struct{})
|
resKit.Quit = make(chan struct{})
|
||||||
resolver := &htlcIncomingContestResolver{
|
resolver := &htlcIncomingContestResolver{
|
||||||
htlcExpiry: htlc.RefundTimeout,
|
htlcExpiry: htlc.RefundTimeout,
|
||||||
|
circuitKey: circuitKey,
|
||||||
htlcSuccessResolver: htlcSuccessResolver{
|
htlcSuccessResolver: htlcSuccessResolver{
|
||||||
htlcResolution: resolution,
|
htlcResolution: resolution,
|
||||||
broadcastHeight: height,
|
broadcastHeight: height,
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ type htlcIncomingContestResolver struct {
|
|||||||
// successfully.
|
// successfully.
|
||||||
htlcExpiry uint32
|
htlcExpiry uint32
|
||||||
|
|
||||||
|
// circuitKey describes the incoming htlc that is being resolved.
|
||||||
|
circuitKey channeldb.CircuitKey
|
||||||
|
|
||||||
// htlcSuccessResolver is the inner resolver that may be utilized if we
|
// htlcSuccessResolver is the inner resolver that may be utilized if we
|
||||||
// learn of the preimage.
|
// learn of the preimage.
|
||||||
htlcSuccessResolver
|
htlcSuccessResolver
|
||||||
@@ -166,7 +169,7 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
|
|||||||
// identical to HTLC resolution in the link.
|
// identical to HTLC resolution in the link.
|
||||||
event, err := h.Registry.NotifyExitHopHtlc(
|
event, err := h.Registry.NotifyExitHopHtlc(
|
||||||
h.payHash, h.htlcAmt, h.htlcExpiry, currentHeight,
|
h.payHash, h.htlcAmt, h.htlcExpiry, currentHeight,
|
||||||
hodlChan, nil,
|
h.circuitKey, hodlChan, nil,
|
||||||
)
|
)
|
||||||
switch err {
|
switch err {
|
||||||
case channeldb.ErrInvoiceNotFound:
|
case channeldb.ErrInvoiceNotFound:
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import (
|
|||||||
// Registry is an interface which represents the invoice registry.
|
// Registry is an interface which represents the invoice registry.
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
// LookupInvoice attempts to look up an invoice according to its 32
|
// LookupInvoice attempts to look up an invoice according to its 32
|
||||||
// byte payment hash. This method should also reutrn the min final CLTV
|
// byte payment hash.
|
||||||
// delta for this invoice. We'll use this to ensure that the HTLC
|
LookupInvoice(lntypes.Hash) (channeldb.Invoice, error)
|
||||||
// extended to us gives us enough time to settle as we prescribe.
|
|
||||||
LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, error)
|
|
||||||
|
|
||||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the
|
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the
|
||||||
// invoice is a debug invoice, then this method is a noop as debug
|
// invoice is a debug invoice, then this method is a noop as debug
|
||||||
@@ -22,7 +20,7 @@ type Registry interface {
|
|||||||
// the resolution is sent on the passed in hodlChan later.
|
// the resolution is sent on the passed in hodlChan later.
|
||||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||||
expiry uint32, currentHeight int32,
|
expiry uint32, currentHeight int32,
|
||||||
hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
eob []byte) (*invoices.HodlEvent, error)
|
eob []byte) (*invoices.HodlEvent, error)
|
||||||
|
|
||||||
// HodlUnsubscribeAll unsubscribes from all hodl events.
|
// HodlUnsubscribeAll unsubscribes from all hodl events.
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ type mockRegistry struct {
|
|||||||
|
|
||||||
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
||||||
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
paidAmount lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) {
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
|
eob []byte) (*invoices.HodlEvent, error) {
|
||||||
|
|
||||||
r.notifyChan <- notifyExitHopData{
|
r.notifyChan <- notifyExitHopData{
|
||||||
hodlChan: hodlChan,
|
hodlChan: hodlChan,
|
||||||
@@ -38,8 +39,8 @@ func (r *mockRegistry) NotifyExitHopHtlc(payHash lntypes.Hash,
|
|||||||
|
|
||||||
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}
|
func (r *mockRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {}
|
||||||
|
|
||||||
func (r *mockRegistry) LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32,
|
func (r *mockRegistry) LookupInvoice(lntypes.Hash) (channeldb.Invoice,
|
||||||
error) {
|
error) {
|
||||||
|
|
||||||
return channeldb.Invoice{}, 0, channeldb.ErrInvoiceNotFound
|
return channeldb.Invoice{}, channeldb.ErrInvoiceNotFound
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,8 @@ import (
|
|||||||
// which may search, lookup and settle invoices.
|
// which may search, lookup and settle invoices.
|
||||||
type InvoiceDatabase interface {
|
type InvoiceDatabase interface {
|
||||||
// LookupInvoice attempts to look up an invoice according to its 32
|
// LookupInvoice attempts to look up an invoice according to its 32
|
||||||
// byte payment hash. This method should also reutrn the min final CLTV
|
// byte payment hash.
|
||||||
// delta for this invoice. We'll use this to ensure that the HTLC
|
LookupInvoice(lntypes.Hash) (channeldb.Invoice, error)
|
||||||
// extended to us gives us enough time to settle as we prescribe.
|
|
||||||
LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, error)
|
|
||||||
|
|
||||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the
|
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the
|
||||||
// invoice is a debug invoice, then this method is a noop as debug
|
// invoice is a debug invoice, then this method is a noop as debug
|
||||||
@@ -28,7 +26,7 @@ type InvoiceDatabase interface {
|
|||||||
// for decoding purposes.
|
// for decoding purposes.
|
||||||
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
NotifyExitHopHtlc(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi,
|
||||||
expiry uint32, currentHeight int32,
|
expiry uint32, currentHeight int32,
|
||||||
hodlChan chan<- interface{},
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
eob []byte) (*invoices.HodlEvent, error)
|
eob []byte) (*invoices.HodlEvent, error)
|
||||||
|
|
||||||
// CancelInvoice attempts to cancel the invoice corresponding to the
|
// CancelInvoice attempts to cancel the invoice corresponding to the
|
||||||
|
|||||||
+6
-1
@@ -2878,9 +2878,14 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor,
|
|||||||
// receive back a resolution event.
|
// receive back a resolution event.
|
||||||
invoiceHash := lntypes.Hash(pd.RHash)
|
invoiceHash := lntypes.Hash(pd.RHash)
|
||||||
|
|
||||||
|
circuitKey := channeldb.CircuitKey{
|
||||||
|
ChanID: l.ShortChanID(),
|
||||||
|
HtlcID: pd.HtlcIndex,
|
||||||
|
}
|
||||||
|
|
||||||
event, err := l.cfg.Registry.NotifyExitHopHtlc(
|
event, err := l.cfg.Registry.NotifyExitHopHtlc(
|
||||||
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
|
invoiceHash, pd.Amount, pd.Timeout, int32(heightNow),
|
||||||
l.hodlQueue.ChanIn(), eob,
|
circuitKey, l.hodlQueue.ChanIn(), eob,
|
||||||
)
|
)
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ func TestChannelLinkSingleHopPayment(t *testing.T) {
|
|||||||
|
|
||||||
// Check that alice invoice was settled and bandwidth of HTLC
|
// Check that alice invoice was settled and bandwidth of HTLC
|
||||||
// links was changed.
|
// links was changed.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -498,7 +498,7 @@ func testChannelLinkMultiHopPayment(t *testing.T,
|
|||||||
|
|
||||||
// Check that Carol invoice was settled and bandwidth of HTLC
|
// Check that Carol invoice was settled and bandwidth of HTLC
|
||||||
// links were changed.
|
// links were changed.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -912,7 +912,7 @@ func TestUpdateForwardingPolicy(t *testing.T) {
|
|||||||
|
|
||||||
// Carol's invoice should now be shown as settled as the payment
|
// Carol's invoice should now be shown as settled as the payment
|
||||||
// succeeded.
|
// succeeded.
|
||||||
invoice, _, err := n.carolServer.registry.LookupInvoice(payResp)
|
invoice, err := n.carolServer.registry.LookupInvoice(payResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1030,7 +1030,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
|
|||||||
|
|
||||||
// Check that alice invoice wasn't settled and bandwidth of htlc
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
// links hasn't been changed.
|
// links hasn't been changed.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1215,7 +1215,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
|
|||||||
|
|
||||||
// Check that alice invoice wasn't settled and bandwidth of htlc
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
// links hasn't been changed.
|
// links hasn't been changed.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1330,7 +1330,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
|
|||||||
|
|
||||||
// Check that alice invoice wasn't settled and bandwidth of htlc
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
// links hasn't been changed.
|
// links hasn't been changed.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
@@ -3455,7 +3455,7 @@ func TestChannelRetransmission(t *testing.T) {
|
|||||||
|
|
||||||
// Check that alice invoice wasn't settled and
|
// Check that alice invoice wasn't settled and
|
||||||
// bandwidth of htlc links hasn't been changed.
|
// bandwidth of htlc links hasn't been changed.
|
||||||
invoice, _, err = receiver.registry.LookupInvoice(rhash)
|
invoice, err = receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Errorf("unable to get invoice: %v", err)
|
err = errors.Errorf("unable to get invoice: %v", err)
|
||||||
continue
|
continue
|
||||||
@@ -3974,7 +3974,7 @@ func TestChannelLinkAcceptOverpay(t *testing.T) {
|
|||||||
|
|
||||||
// Even though we sent 2x what was asked for, Carol should still have
|
// Even though we sent 2x what was asked for, Carol should still have
|
||||||
// accepted the payment and marked it as settled.
|
// accepted the payment and marked it as settled.
|
||||||
invoice, _, err := receiver.registry.LookupInvoice(rhash)
|
invoice, err := receiver.registry.LookupInvoice(rhash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get invoice: %v", err)
|
t.Fatalf("unable to get invoice: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-8
@@ -768,13 +768,9 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
decodeExpiry := func(invoice string) (uint32, error) {
|
|
||||||
return testInvoiceCltvExpiry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
finalCltvRejectDelta := int32(5)
|
finalCltvRejectDelta := int32(5)
|
||||||
|
|
||||||
registry := invoices.NewRegistry(cdb, decodeExpiry, finalCltvRejectDelta)
|
registry := invoices.NewRegistry(cdb, finalCltvRejectDelta)
|
||||||
registry.Start()
|
registry.Start()
|
||||||
|
|
||||||
return &mockInvoiceRegistry{
|
return &mockInvoiceRegistry{
|
||||||
@@ -783,7 +779,9 @@ func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) {
|
func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (
|
||||||
|
channeldb.Invoice, error) {
|
||||||
|
|
||||||
return i.registry.LookupInvoice(rHash)
|
return i.registry.LookupInvoice(rHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,10 +791,11 @@ func (i *mockInvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error
|
|||||||
|
|
||||||
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
func (i *mockInvoiceRegistry) NotifyExitHopHtlc(rhash lntypes.Hash,
|
||||||
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
amt lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
hodlChan chan<- interface{}, eob []byte) (*invoices.HodlEvent, error) {
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
|
eob []byte) (*invoices.HodlEvent, error) {
|
||||||
|
|
||||||
event, err := i.registry.NotifyExitHopHtlc(
|
event, err := i.registry.NotifyExitHopHtlc(
|
||||||
rhash, amt, expiry, currentHeight, hodlChan, eob,
|
rhash, amt, expiry, currentHeight, circuitKey, hodlChan, eob,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ func generatePaymentWithPreimage(invoiceAmt, htlcAmt lnwire.MilliSatoshi,
|
|||||||
Value: invoiceAmt,
|
Value: invoiceAmt,
|
||||||
PaymentPreimage: preimage,
|
PaymentPreimage: preimage,
|
||||||
},
|
},
|
||||||
|
FinalCltvDelta: testInvoiceCltvExpiry,
|
||||||
}
|
}
|
||||||
|
|
||||||
htlc := &lnwire.UpdateAddHTLC{
|
htlc := &lnwire.UpdateAddHTLC{
|
||||||
|
|||||||
+190
-161
@@ -2,7 +2,6 @@ package invoices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@@ -25,6 +24,13 @@ var (
|
|||||||
// ErrShuttingDown is returned when an operation failed because the
|
// ErrShuttingDown is returned when an operation failed because the
|
||||||
// invoice registry is shutting down.
|
// invoice registry is shutting down.
|
||||||
ErrShuttingDown = errors.New("invoice registry shutting down")
|
ErrShuttingDown = errors.New("invoice registry shutting down")
|
||||||
|
|
||||||
|
// errNoUpdate is returned when no invoice updated is required.
|
||||||
|
errNoUpdate = errors.New("no update needed")
|
||||||
|
|
||||||
|
// errReplayedHtlc is returned if the htlc is already recorded on the
|
||||||
|
// invoice.
|
||||||
|
errReplayedHtlc = errors.New("replayed htlc")
|
||||||
)
|
)
|
||||||
|
|
||||||
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is
|
// HodlEvent describes how an htlc should be resolved. If HodlEvent.Preimage is
|
||||||
@@ -58,10 +64,6 @@ type InvoiceRegistry struct {
|
|||||||
// new single invoice subscriptions are carried.
|
// new single invoice subscriptions are carried.
|
||||||
invoiceEvents chan interface{}
|
invoiceEvents chan interface{}
|
||||||
|
|
||||||
// decodeFinalCltvExpiry is a function used to decode the final expiry
|
|
||||||
// value from the payment request.
|
|
||||||
decodeFinalCltvExpiry func(invoice string) (uint32, error)
|
|
||||||
|
|
||||||
// subscriptions is a map from a payment hash to a list of subscribers.
|
// subscriptions is a map from a payment hash to a list of subscribers.
|
||||||
// It is used for efficient notification of links.
|
// It is used for efficient notification of links.
|
||||||
hodlSubscriptions map[lntypes.Hash]map[chan<- interface{}]struct{}
|
hodlSubscriptions map[lntypes.Hash]map[chan<- interface{}]struct{}
|
||||||
@@ -85,8 +87,7 @@ type InvoiceRegistry struct {
|
|||||||
// wraps the persistent on-disk invoice storage with an additional in-memory
|
// wraps the persistent on-disk invoice storage with an additional in-memory
|
||||||
// layer. The in-memory layer is in place such that debug invoices can be added
|
// layer. The in-memory layer is in place such that debug invoices can be added
|
||||||
// which are volatile yet available system wide within the daemon.
|
// which are volatile yet available system wide within the daemon.
|
||||||
func NewRegistry(cdb *channeldb.DB, decodeFinalCltvExpiry func(invoice string) (
|
func NewRegistry(cdb *channeldb.DB, finalCltvRejectDelta int32) *InvoiceRegistry {
|
||||||
uint32, error), finalCltvRejectDelta int32) *InvoiceRegistry {
|
|
||||||
|
|
||||||
return &InvoiceRegistry{
|
return &InvoiceRegistry{
|
||||||
cdb: cdb,
|
cdb: cdb,
|
||||||
@@ -97,7 +98,6 @@ func NewRegistry(cdb *channeldb.DB, decodeFinalCltvExpiry func(invoice string) (
|
|||||||
invoiceEvents: make(chan interface{}, 100),
|
invoiceEvents: make(chan interface{}, 100),
|
||||||
hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}),
|
hodlSubscriptions: make(map[lntypes.Hash]map[chan<- interface{}]struct{}),
|
||||||
hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}),
|
hodlReverseSubscriptions: make(map[chan<- interface{}]map[lntypes.Hash]struct{}),
|
||||||
decodeFinalCltvExpiry: decodeFinalCltvExpiry,
|
|
||||||
finalCltvRejectDelta: finalCltvRejectDelta,
|
finalCltvRejectDelta: finalCltvRejectDelta,
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
}
|
}
|
||||||
@@ -404,76 +404,15 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LookupInvoice looks up an invoice by its payment hash (R-Hash), if found
|
// LookupInvoice looks up an invoice by its payment hash (R-Hash), if found
|
||||||
// then we're able to pull the funds pending within an HTLC. We'll also return
|
// then we're able to pull the funds pending within an HTLC.
|
||||||
// what the expected min final CLTV delta is, pre-parsed from the payment
|
|
||||||
// request. This may be used by callers to determine if an HTLC is well formed
|
|
||||||
// according to the cltv delta.
|
|
||||||
//
|
//
|
||||||
// TODO(roasbeef): ignore if settled?
|
// TODO(roasbeef): ignore if settled?
|
||||||
func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) {
|
func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||||
|
error) {
|
||||||
|
|
||||||
// We'll check the database to see if there's an existing matching
|
// We'll check the database to see if there's an existing matching
|
||||||
// invoice.
|
// invoice.
|
||||||
invoice, err := i.cdb.LookupInvoice(rHash)
|
return i.cdb.LookupInvoice(rHash)
|
||||||
if err != nil {
|
|
||||||
return channeldb.Invoice{}, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
expiry, err := i.decodeFinalCltvExpiry(string(invoice.PaymentRequest))
|
|
||||||
if err != nil {
|
|
||||||
return channeldb.Invoice{}, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return invoice, expiry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkHtlcParameters is a callback used inside invoice db transactions to
|
|
||||||
// atomically check-and-update an invoice.
|
|
||||||
func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice,
|
|
||||||
amtPaid lnwire.MilliSatoshi, htlcExpiry uint32, currentHeight int32) error {
|
|
||||||
|
|
||||||
// If the invoice is already canceled, there is no further checking to
|
|
||||||
// do.
|
|
||||||
if invoice.Terms.State == channeldb.ContractCanceled {
|
|
||||||
return channeldb.ErrInvoiceAlreadyCanceled
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a payment has already been made, we only accept more payments if
|
|
||||||
// the amount is the exact same. This prevents probing with small
|
|
||||||
// amounts on settled invoices to find out the receiver node.
|
|
||||||
if invoice.AmtPaid != 0 && amtPaid != invoice.AmtPaid {
|
|
||||||
return ErrInvoiceAmountTooLow
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return early in case the invoice was already accepted or settled. We
|
|
||||||
// don't want to check the expiry again, because it may be that we are
|
|
||||||
// just restarting.
|
|
||||||
switch invoice.Terms.State {
|
|
||||||
case channeldb.ContractAccepted:
|
|
||||||
return channeldb.ErrInvoiceAlreadyAccepted
|
|
||||||
case channeldb.ContractSettled:
|
|
||||||
return channeldb.ErrInvoiceAlreadySettled
|
|
||||||
}
|
|
||||||
|
|
||||||
// The invoice is still open. Check the expiry.
|
|
||||||
expiry, err := i.decodeFinalCltvExpiry(string(invoice.PaymentRequest))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if htlcExpiry < uint32(currentHeight+i.finalCltvRejectDelta) {
|
|
||||||
return ErrInvoiceExpiryTooSoon
|
|
||||||
}
|
|
||||||
|
|
||||||
if htlcExpiry < uint32(currentHeight)+expiry {
|
|
||||||
return ErrInvoiceExpiryTooSoon
|
|
||||||
}
|
|
||||||
|
|
||||||
// If an invoice amount is specified, check that enough is paid.
|
|
||||||
if invoice.Terms.Value > 0 && amtPaid < invoice.Terms.Value {
|
|
||||||
return ErrInvoiceAmountTooLow
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a
|
// NotifyExitHopHtlc attempts to mark an invoice as settled. If the invoice is a
|
||||||
@@ -489,108 +428,155 @@ func (i *InvoiceRegistry) checkHtlcParameters(invoice *channeldb.Invoice,
|
|||||||
// prevent deadlock.
|
// prevent deadlock.
|
||||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||||
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||||
hodlChan chan<- interface{}, eob []byte) (*HodlEvent, error) {
|
circuitKey channeldb.CircuitKey, hodlChan chan<- interface{},
|
||||||
|
eob []byte) (*HodlEvent, error) {
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
|
|
||||||
debugLog := func(s string) {
|
debugLog := func(s string) {
|
||||||
log.Debugf("Invoice(%x): %v, amt=%v, expiry=%v",
|
log.Debugf("Invoice(%x): %v, amt=%v, expiry=%v, circuit=%v",
|
||||||
rHash[:], s, amtPaid, expiry)
|
rHash[:], s, amtPaid, expiry, circuitKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this isn't a debug invoice, then we'll attempt to settle an
|
// Default is to not update subscribers after the invoice update.
|
||||||
// invoice matching this rHash on disk (if one exists).
|
updateSubscribers := false
|
||||||
invoice, err := i.cdb.AcceptOrSettleInvoice(
|
|
||||||
rHash, amtPaid,
|
|
||||||
func(inv *channeldb.Invoice) error {
|
|
||||||
return i.checkHtlcParameters(
|
|
||||||
inv, amtPaid, expiry, currentHeight,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
switch err {
|
|
||||||
|
|
||||||
// If invoice is already settled, settle htlc. This means we accept more
|
updateInvoice := func(inv *channeldb.Invoice) (
|
||||||
// payments to the same invoice hash.
|
*channeldb.InvoiceUpdateDesc, error) {
|
||||||
//
|
|
||||||
// NOTE: Though our recovery and forwarding logic is predominately
|
|
||||||
// batched, settling invoices happens iteratively. We may reject one of
|
|
||||||
// two payments for the same rhash at first, but then restart and reject
|
|
||||||
// both after seeing that the invoice has been settled. Without any
|
|
||||||
// record of which one settles first, it is ambiguous as to which one
|
|
||||||
// actually settled the invoice. Thus, by accepting all payments, we
|
|
||||||
// eliminate the race condition that can lead to this inconsistency.
|
|
||||||
//
|
|
||||||
// TODO(conner): track ownership of settlements to properly recover from
|
|
||||||
// failures? or add batch invoice settlement
|
|
||||||
case channeldb.ErrInvoiceAlreadySettled:
|
|
||||||
debugLog("accepting duplicate payment to settled invoice")
|
|
||||||
|
|
||||||
|
// Don't update the invoice when this is a replayed htlc.
|
||||||
|
htlc, ok := inv.Htlcs[circuitKey]
|
||||||
|
if ok {
|
||||||
|
switch htlc.State {
|
||||||
|
case channeldb.HtlcStateCancelled:
|
||||||
|
debugLog("replayed htlc to canceled invoice")
|
||||||
|
|
||||||
|
case channeldb.HtlcStateAccepted:
|
||||||
|
debugLog("replayed htlc to accepted invoice")
|
||||||
|
|
||||||
|
case channeldb.HtlcStateSettled:
|
||||||
|
debugLog("replayed htlc to settled invoice")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unexpected htlc state")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errNoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the invoice is already canceled, there is no further
|
||||||
|
// checking to do.
|
||||||
|
if inv.Terms.State == channeldb.ContractCanceled {
|
||||||
|
debugLog("invoice already canceled")
|
||||||
|
return nil, errNoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an invoice amount is specified, check that enough
|
||||||
|
// is paid. Also check this for duplicate payments if
|
||||||
|
// the invoice is already settled or accepted.
|
||||||
|
if inv.Terms.Value > 0 && amtPaid < inv.Terms.Value {
|
||||||
|
debugLog("amount too low")
|
||||||
|
return nil, errNoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// The invoice is still open. Check the expiry.
|
||||||
|
if expiry < uint32(currentHeight+i.finalCltvRejectDelta) {
|
||||||
|
debugLog("expiry too soon")
|
||||||
|
return nil, errNoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
if expiry < uint32(currentHeight+inv.FinalCltvDelta) {
|
||||||
|
debugLog("expiry too soon")
|
||||||
|
return nil, errNoUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record HTLC in the invoice database.
|
||||||
|
newHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{
|
||||||
|
circuitKey: {
|
||||||
|
Amt: amtPaid,
|
||||||
|
Expiry: expiry,
|
||||||
|
AcceptHeight: currentHeight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
update := channeldb.InvoiceUpdateDesc{
|
||||||
|
Htlcs: newHtlcs,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't update invoice state if we are accepting a duplicate
|
||||||
|
// payment. We do accept or settle the HTLC.
|
||||||
|
switch inv.Terms.State {
|
||||||
|
case channeldb.ContractAccepted:
|
||||||
|
debugLog("accepting duplicate payment to accepted invoice")
|
||||||
|
update.State = channeldb.ContractAccepted
|
||||||
|
return &update, nil
|
||||||
|
|
||||||
|
case channeldb.ContractSettled:
|
||||||
|
debugLog("accepting duplicate payment to settled invoice")
|
||||||
|
update.State = channeldb.ContractSettled
|
||||||
|
return &update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if we can settle or this is an hold invoice and
|
||||||
|
// we need to wait for the preimage.
|
||||||
|
holdInvoice := inv.Terms.PaymentPreimage == channeldb.UnknownPreimage
|
||||||
|
if holdInvoice {
|
||||||
|
debugLog("accepted")
|
||||||
|
update.State = channeldb.ContractAccepted
|
||||||
|
} else {
|
||||||
|
debugLog("settled")
|
||||||
|
update.Preimage = inv.Terms.PaymentPreimage
|
||||||
|
update.State = channeldb.ContractSettled
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSubscribers = true
|
||||||
|
|
||||||
|
return &update, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll attempt to settle an invoice matching this rHash on disk (if
|
||||||
|
// one exists). The callback will set the resolution action that is
|
||||||
|
// returned to the link or contract resolver.
|
||||||
|
invoice, err := i.cdb.UpdateInvoice(rHash, updateInvoice)
|
||||||
|
if err != nil && err != errNoUpdate {
|
||||||
|
debugLog(err.Error())
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateSubscribers {
|
||||||
|
i.notifyClients(rHash, invoice, invoice.Terms.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect latest htlc state on the invoice.
|
||||||
|
invoiceHtlc, ok := invoice.Htlcs[circuitKey]
|
||||||
|
|
||||||
|
// If it isn't recorded, cancel htlc.
|
||||||
|
if !ok {
|
||||||
|
return &HodlEvent{
|
||||||
|
Hash: rHash,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch invoiceHtlc.State {
|
||||||
|
case channeldb.HtlcStateCancelled:
|
||||||
|
return &HodlEvent{
|
||||||
|
Hash: rHash,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case channeldb.HtlcStateSettled:
|
||||||
return &HodlEvent{
|
return &HodlEvent{
|
||||||
Hash: rHash,
|
Hash: rHash,
|
||||||
Preimage: &invoice.Terms.PaymentPreimage,
|
Preimage: &invoice.Terms.PaymentPreimage,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
// If invoice is already canceled, cancel htlc.
|
case channeldb.HtlcStateAccepted:
|
||||||
case channeldb.ErrInvoiceAlreadyCanceled:
|
|
||||||
debugLog("invoice already canceled")
|
|
||||||
|
|
||||||
return &HodlEvent{
|
|
||||||
Hash: rHash,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// If invoice is already accepted, add this htlc to the list of
|
|
||||||
// subscribers.
|
|
||||||
case channeldb.ErrInvoiceAlreadyAccepted:
|
|
||||||
debugLog("accepting duplicate payment to accepted invoice")
|
|
||||||
|
|
||||||
i.hodlSubscribe(hodlChan, rHash)
|
i.hodlSubscribe(hodlChan, rHash)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
||||||
// If there are not enough blocks left, cancel the htlc.
|
|
||||||
case ErrInvoiceExpiryTooSoon:
|
|
||||||
debugLog("expiry too soon")
|
|
||||||
|
|
||||||
return &HodlEvent{
|
|
||||||
Hash: rHash,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// If there are not enough blocks left, cancel the htlc.
|
|
||||||
case ErrInvoiceAmountTooLow:
|
|
||||||
debugLog("amount too low")
|
|
||||||
|
|
||||||
return &HodlEvent{
|
|
||||||
Hash: rHash,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
// If this call settled the invoice, settle the htlc. Otherwise
|
|
||||||
// subscribe for a future hodl event.
|
|
||||||
case nil:
|
|
||||||
i.notifyClients(rHash, invoice, invoice.Terms.State)
|
|
||||||
switch invoice.Terms.State {
|
|
||||||
case channeldb.ContractSettled:
|
|
||||||
debugLog("settled")
|
|
||||||
|
|
||||||
return &HodlEvent{
|
|
||||||
Hash: rHash,
|
|
||||||
Preimage: &invoice.Terms.PaymentPreimage,
|
|
||||||
}, nil
|
|
||||||
case channeldb.ContractAccepted:
|
|
||||||
debugLog("accepted")
|
|
||||||
|
|
||||||
// Subscribe to updates to this invoice.
|
|
||||||
i.hodlSubscribe(hodlChan, rHash)
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected invoice state %v",
|
|
||||||
invoice.Terms.State)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
debugLog(err.Error())
|
panic("unknown action")
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,13 +585,31 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
|||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
|
|
||||||
invoice, err := i.cdb.SettleHoldInvoice(preimage)
|
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||||
|
*channeldb.InvoiceUpdateDesc, error) {
|
||||||
|
|
||||||
|
switch invoice.Terms.State {
|
||||||
|
case channeldb.ContractOpen:
|
||||||
|
return nil, channeldb.ErrInvoiceStillOpen
|
||||||
|
case channeldb.ContractCanceled:
|
||||||
|
return nil, channeldb.ErrInvoiceAlreadyCanceled
|
||||||
|
case channeldb.ContractSettled:
|
||||||
|
return nil, channeldb.ErrInvoiceAlreadySettled
|
||||||
|
}
|
||||||
|
|
||||||
|
return &channeldb.InvoiceUpdateDesc{
|
||||||
|
State: channeldb.ContractSettled,
|
||||||
|
Preimage: preimage,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := preimage.Hash()
|
||||||
|
invoice, err := i.cdb.UpdateInvoice(hash, updateInvoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err)
|
log.Errorf("SettleHodlInvoice with preimage %v: %v", preimage, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := preimage.Hash()
|
|
||||||
log.Debugf("Invoice(%v): settled with preimage %v", hash,
|
log.Debugf("Invoice(%v): settled with preimage %v", hash,
|
||||||
invoice.Terms.PaymentPreimage)
|
invoice.Terms.PaymentPreimage)
|
||||||
|
|
||||||
@@ -626,7 +630,32 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
|||||||
|
|
||||||
log.Debugf("Invoice(%v): canceling invoice", payHash)
|
log.Debugf("Invoice(%v): canceling invoice", payHash)
|
||||||
|
|
||||||
invoice, err := i.cdb.CancelInvoice(payHash)
|
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||||
|
*channeldb.InvoiceUpdateDesc, error) {
|
||||||
|
|
||||||
|
switch invoice.Terms.State {
|
||||||
|
case channeldb.ContractSettled:
|
||||||
|
return nil, channeldb.ErrInvoiceAlreadySettled
|
||||||
|
case channeldb.ContractCanceled:
|
||||||
|
return nil, channeldb.ErrInvoiceAlreadyCanceled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark individual held htlcs as cancelled.
|
||||||
|
canceledHtlcs := make(
|
||||||
|
map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc,
|
||||||
|
)
|
||||||
|
for key := range invoice.Htlcs {
|
||||||
|
canceledHtlcs[key] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move invoice to the canceled state.
|
||||||
|
return &channeldb.InvoiceUpdateDesc{
|
||||||
|
Htlcs: canceledHtlcs,
|
||||||
|
State: channeldb.ContractCanceled,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice, err := i.cdb.UpdateInvoice(payHash, updateInvoice)
|
||||||
|
|
||||||
// Implement idempotency by returning success if the invoice was already
|
// Implement idempotency by returning success if the invoice was already
|
||||||
// canceled.
|
// canceled.
|
||||||
|
|||||||
@@ -21,17 +21,15 @@ var (
|
|||||||
|
|
||||||
hash = preimage.Hash()
|
hash = preimage.Hash()
|
||||||
|
|
||||||
testInvoiceExpiry = uint32(3)
|
testHtlcExpiry = uint32(5)
|
||||||
|
|
||||||
testCurrentHeight = int32(0)
|
testInvoiceCltvDelta = uint32(4)
|
||||||
|
|
||||||
testFinalCltvRejectDelta = int32(3)
|
testFinalCltvRejectDelta = int32(4)
|
||||||
|
|
||||||
|
testCurrentHeight = int32(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeExpiry(payReq string) (uint32, error) {
|
|
||||||
return uint32(testInvoiceExpiry), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testInvoice = &channeldb.Invoice{
|
testInvoice = &channeldb.Invoice{
|
||||||
Terms: channeldb.ContractTerm{
|
Terms: channeldb.ContractTerm{
|
||||||
@@ -48,7 +46,7 @@ func newTestContext(t *testing.T) (*InvoiceRegistry, func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate and start the invoice registry.
|
// Instantiate and start the invoice registry.
|
||||||
registry := NewRegistry(cdb, decodeExpiry, testFinalCltvRejectDelta)
|
registry := NewRegistry(cdb, testFinalCltvRejectDelta)
|
||||||
|
|
||||||
err = registry.Start()
|
err = registry.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,6 +60,15 @@ func newTestContext(t *testing.T) (*InvoiceRegistry, func()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCircuitKey(htlcID uint64) channeldb.CircuitKey {
|
||||||
|
return channeldb.CircuitKey{
|
||||||
|
ChanID: lnwire.ShortChannelID{
|
||||||
|
BlockHeight: 1, TxIndex: 2, TxPosition: 3,
|
||||||
|
},
|
||||||
|
HtlcID: htlcID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
// TestSettleInvoice tests settling of an invoice and related notifications.
|
||||||
func TestSettleInvoice(t *testing.T) {
|
func TestSettleInvoice(t *testing.T) {
|
||||||
registry, cleanup := newTestContext(t)
|
registry, cleanup := newTestContext(t)
|
||||||
@@ -119,7 +126,8 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
// Settle invoice with a slightly higher amount.
|
// Settle invoice with a slightly higher amount.
|
||||||
amtPaid := lnwire.MilliSatoshi(100500)
|
amtPaid := lnwire.MilliSatoshi(100500)
|
||||||
_, err = registry.NotifyExitHopHtlc(
|
_, err = registry.NotifyExitHopHtlc(
|
||||||
hash, amtPaid, testInvoiceExpiry, 0, hodlChan, nil,
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -151,11 +159,11 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
t.Fatal("no update received")
|
t.Fatal("no update received")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to settle again. We need this idempotent behaviour after a
|
// Try to settle again with the same htlc id. We need this idempotent
|
||||||
// restart.
|
// behaviour after a restart.
|
||||||
event, err := registry.NotifyExitHopHtlc(
|
event, err := registry.NotifyExitHopHtlc(
|
||||||
hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan,
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
nil,
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
@@ -164,12 +172,25 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
t.Fatal("expected settle event")
|
t.Fatal("expected settle event")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to settle again with a higher amount. This should result in a
|
// Try to settle again with a new higher-valued htlc. This payment
|
||||||
// cancel event because after a restart the amount should still be the
|
// should also be accepted, to prevent any change in behaviour for a
|
||||||
// same. New HTLCs with a different amount should be rejected.
|
// paid invoice that may open up a probe vector.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
event, err = registry.NotifyExitHopHtlc(
|
||||||
hash, amtPaid+600, testInvoiceExpiry, testCurrentHeight,
|
hash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
|
||||||
hodlChan, nil,
|
getCircuitKey(1), hodlChan, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
|
}
|
||||||
|
if event.Preimage == nil {
|
||||||
|
t.Fatal("expected settle event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to settle again with a lower amount. This should fail just as it
|
||||||
|
// would have failed if it were the first payment.
|
||||||
|
event, err = registry.NotifyExitHopHtlc(
|
||||||
|
hash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
|
||||||
|
getCircuitKey(2), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
||||||
@@ -178,26 +199,14 @@ func TestSettleInvoice(t *testing.T) {
|
|||||||
t.Fatal("expected cancel event")
|
t.Fatal("expected cancel event")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to settle again with a lower amount. This should show the same
|
// Check that settled amount is equal to the sum of values of the htlcs
|
||||||
// behaviour as settling with a higher amount.
|
// 0 and 1.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
inv, err := registry.LookupInvoice(hash)
|
||||||
hash, amtPaid-600, testInvoiceExpiry, testCurrentHeight,
|
|
||||||
hodlChan, nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
|
||||||
}
|
|
||||||
if event.Preimage != nil {
|
|
||||||
t.Fatal("expected cancel event")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that settled amount remains unchanged.
|
|
||||||
inv, _, err := registry.LookupInvoice(hash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if inv.AmtPaid != amtPaid {
|
if inv.AmtPaid != amtPaid+amtPaid+600 {
|
||||||
t.Fatal("expected amount to be unchanged")
|
t.Fatal("amount incorrect")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to cancel.
|
// Try to cancel.
|
||||||
@@ -305,7 +314,8 @@ func TestCancelInvoice(t *testing.T) {
|
|||||||
// succeed.
|
// succeed.
|
||||||
hodlChan := make(chan interface{})
|
hodlChan := make(chan interface{})
|
||||||
event, err := registry.NotifyExitHopHtlc(
|
event, err := registry.NotifyExitHopHtlc(
|
||||||
hash, amt, testInvoiceExpiry, testCurrentHeight, hodlChan, nil,
|
hash, amt, testHtlcExpiry, testCurrentHeight,
|
||||||
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("expected settlement of a canceled invoice to succeed")
|
t.Fatal("expected settlement of a canceled invoice to succeed")
|
||||||
@@ -324,7 +334,7 @@ func TestHoldInvoice(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Instantiate and start the invoice registry.
|
// Instantiate and start the invoice registry.
|
||||||
registry := NewRegistry(cdb, decodeExpiry, testFinalCltvRejectDelta)
|
registry := NewRegistry(cdb, testFinalCltvRejectDelta)
|
||||||
|
|
||||||
err = registry.Start()
|
err = registry.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -381,26 +391,52 @@ func TestHoldInvoice(t *testing.T) {
|
|||||||
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
||||||
// should be possible.
|
// should be possible.
|
||||||
event, err := registry.NotifyExitHopHtlc(
|
event, err := registry.NotifyExitHopHtlc(
|
||||||
hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan,
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
nil,
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if event != nil {
|
||||||
t.Fatalf("unexpect direct settle")
|
t.Fatalf("expected htlc to be held")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test idempotency.
|
// Test idempotency.
|
||||||
event, err = registry.NotifyExitHopHtlc(
|
event, err = registry.NotifyExitHopHtlc(
|
||||||
hash, amtPaid, testInvoiceExpiry, testCurrentHeight, hodlChan,
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
||||||
nil,
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected settle to succeed but got %v", err)
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
}
|
}
|
||||||
if event != nil {
|
if event != nil {
|
||||||
t.Fatalf("unexpect direct settle")
|
t.Fatalf("expected htlc to be held")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test replay at a higher height. We expect the same result because it
|
||||||
|
// is a replay.
|
||||||
|
event, err = registry.NotifyExitHopHtlc(
|
||||||
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
|
||||||
|
getCircuitKey(0), hodlChan, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
|
}
|
||||||
|
if event != nil {
|
||||||
|
t.Fatalf("expected htlc to be held")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a new htlc coming in that doesn't meet the final cltv delta
|
||||||
|
// requirement. It should be rejected.
|
||||||
|
event, err = registry.NotifyExitHopHtlc(
|
||||||
|
hash, amtPaid, 1, testCurrentHeight,
|
||||||
|
getCircuitKey(1), hodlChan, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected settle to succeed but got %v", err)
|
||||||
|
}
|
||||||
|
if event == nil || event.Preimage != nil {
|
||||||
|
t.Fatalf("expected htlc to be cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect the accepted state to be sent to the single invoice
|
// We expect the accepted state to be sent to the single invoice
|
||||||
@@ -433,6 +469,10 @@ func TestHoldInvoice(t *testing.T) {
|
|||||||
t.Fatalf("expected state ContractSettled, but got %v",
|
t.Fatalf("expected state ContractSettled, but got %v",
|
||||||
settledInvoice.Terms.State)
|
settledInvoice.Terms.State)
|
||||||
}
|
}
|
||||||
|
if settledInvoice.AmtPaid != amtPaid {
|
||||||
|
t.Fatalf("expected amount to be %v, but got %v",
|
||||||
|
amtPaid, settledInvoice.AmtPaid)
|
||||||
|
}
|
||||||
|
|
||||||
update = <-subscription.Updates
|
update = <-subscription.Updates
|
||||||
if update.Terms.State != channeldb.ContractSettled {
|
if update.Terms.State != channeldb.ContractSettled {
|
||||||
@@ -490,7 +530,8 @@ func TestUnknownInvoice(t *testing.T) {
|
|||||||
hodlChan := make(chan interface{})
|
hodlChan := make(chan interface{})
|
||||||
amt := lnwire.MilliSatoshi(100000)
|
amt := lnwire.MilliSatoshi(100000)
|
||||||
_, err := registry.NotifyExitHopHtlc(
|
_, err := registry.NotifyExitHopHtlc(
|
||||||
hash, amt, testInvoiceExpiry, testCurrentHeight, hodlChan, nil,
|
hash, amt, testHtlcExpiry, testCurrentHeight,
|
||||||
|
getCircuitKey(0), hodlChan, nil,
|
||||||
)
|
)
|
||||||
if err != channeldb.ErrInvoiceNotFound {
|
if err != channeldb.ErrInvoiceNotFound {
|
||||||
t.Fatal("expected invoice not found error")
|
t.Fatal("expected invoice not found error")
|
||||||
|
|||||||
@@ -394,6 +394,8 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
|
|||||||
Memo: []byte(invoice.Memo),
|
Memo: []byte(invoice.Memo),
|
||||||
Receipt: invoice.Receipt,
|
Receipt: invoice.Receipt,
|
||||||
PaymentRequest: []byte(payReqString),
|
PaymentRequest: []byte(payReqString),
|
||||||
|
FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()),
|
||||||
|
Expiry: payReq.Expiry(),
|
||||||
Terms: channeldb.ContractTerm{
|
Terms: channeldb.ContractTerm{
|
||||||
Value: amtMSat,
|
Value: amtMSat,
|
||||||
PaymentPreimage: paymentPreimage,
|
PaymentPreimage: paymentPreimage,
|
||||||
|
|||||||
@@ -36,13 +36,6 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||||||
settleDate = invoice.SettleDate.Unix()
|
settleDate = invoice.SettleDate.Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expiry time will default to 3600 seconds if not specified
|
|
||||||
// explicitly.
|
|
||||||
expiry := int64(decoded.Expiry().Seconds())
|
|
||||||
|
|
||||||
// The expiry will default to 9 blocks if not specified explicitly.
|
|
||||||
cltvExpiry := decoded.MinFinalCLTVExpiry()
|
|
||||||
|
|
||||||
// Convert between the `lnrpc` and `routing` types.
|
// Convert between the `lnrpc` and `routing` types.
|
||||||
routeHints := CreateRPCRouteHints(decoded.RouteHints)
|
routeHints := CreateRPCRouteHints(decoded.RouteHints)
|
||||||
|
|
||||||
@@ -67,6 +60,38 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||||||
invoice.Terms.State)
|
invoice.Terms.State)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpcHtlcs := make([]*lnrpc.InvoiceHTLC, 0, len(invoice.Htlcs))
|
||||||
|
for key, htlc := range invoice.Htlcs {
|
||||||
|
var state lnrpc.InvoiceHTLCState
|
||||||
|
switch htlc.State {
|
||||||
|
case channeldb.HtlcStateAccepted:
|
||||||
|
state = lnrpc.InvoiceHTLCState_ACCEPTED
|
||||||
|
case channeldb.HtlcStateSettled:
|
||||||
|
state = lnrpc.InvoiceHTLCState_SETTLED
|
||||||
|
case channeldb.HtlcStateCancelled:
|
||||||
|
state = lnrpc.InvoiceHTLCState_CANCELLED
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown state %v", htlc.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only report resolved times if htlc is resolved.
|
||||||
|
if htlc.State != channeldb.HtlcStateAccepted {
|
||||||
|
rpcHtlc.ResolveTime = htlc.ResolveTime.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcHtlcs = append(rpcHtlcs, &rpcHtlc)
|
||||||
|
}
|
||||||
|
|
||||||
rpcInvoice := &lnrpc.Invoice{
|
rpcInvoice := &lnrpc.Invoice{
|
||||||
Memo: string(invoice.Memo[:]),
|
Memo: string(invoice.Memo[:]),
|
||||||
Receipt: invoice.Receipt[:],
|
Receipt: invoice.Receipt[:],
|
||||||
@@ -77,8 +102,8 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||||||
Settled: isSettled,
|
Settled: isSettled,
|
||||||
PaymentRequest: paymentRequest,
|
PaymentRequest: paymentRequest,
|
||||||
DescriptionHash: descHash,
|
DescriptionHash: descHash,
|
||||||
Expiry: expiry,
|
Expiry: int64(invoice.Expiry.Seconds()),
|
||||||
CltvExpiry: cltvExpiry,
|
CltvExpiry: uint64(invoice.FinalCltvDelta),
|
||||||
FallbackAddr: fallbackAddr,
|
FallbackAddr: fallbackAddr,
|
||||||
RouteHints: routeHints,
|
RouteHints: routeHints,
|
||||||
AddIndex: invoice.AddIndex,
|
AddIndex: invoice.AddIndex,
|
||||||
@@ -88,6 +113,7 @@ func CreateRPCInvoice(invoice *channeldb.Invoice,
|
|||||||
AmtPaidMsat: int64(invoice.AmtPaid),
|
AmtPaidMsat: int64(invoice.AmtPaid),
|
||||||
AmtPaid: int64(invoice.AmtPaid),
|
AmtPaid: int64(invoice.AmtPaid),
|
||||||
State: state,
|
State: state,
|
||||||
|
Htlcs: rpcHtlcs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if preimage != channeldb.UnknownPreimage {
|
if preimage != channeldb.UnknownPreimage {
|
||||||
|
|||||||
+697
-546
File diff suppressed because it is too large.
Load diff
@@ -2117,6 +2117,42 @@ message Invoice {
|
|||||||
The state the invoice is in.
|
The state the invoice is in.
|
||||||
*/
|
*/
|
||||||
InvoiceState state = 21 [json_name = "state"];
|
InvoiceState state = 21 [json_name = "state"];
|
||||||
|
|
||||||
|
/// List of HTLCs paying to this invoice [EXPERIMENTAL].
|
||||||
|
repeated InvoiceHTLC htlcs = 22 [json_name = "htlcs"];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InvoiceHTLCState {
|
||||||
|
ACCEPTED = 0;
|
||||||
|
SETTLED = 1;
|
||||||
|
CANCELLED = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Details of an HTLC that paid to an invoice
|
||||||
|
message InvoiceHTLC {
|
||||||
|
/// Short channel id over which the htlc was received.
|
||||||
|
uint64 chan_id = 1 [json_name = "chan_id"];
|
||||||
|
|
||||||
|
/// Index identifying the htlc on the channel.
|
||||||
|
uint64 htlc_index = 2 [json_name = "htlc_index"];
|
||||||
|
|
||||||
|
/// The amount of the htlc in msat.
|
||||||
|
uint64 amt_msat = 3 [json_name = "amt_msat"];
|
||||||
|
|
||||||
|
/// Block height at which this htlc was accepted.
|
||||||
|
int32 accept_height = 4 [json_name = "accept_height"];
|
||||||
|
|
||||||
|
/// Time at which this htlc was accepted.
|
||||||
|
int64 accept_time = 5 [json_name = "accept_time"];
|
||||||
|
|
||||||
|
/// Time at which this htlc was settled or cancelled.
|
||||||
|
int64 resolve_time = 6 [json_name = "resolve_time"];
|
||||||
|
|
||||||
|
/// Block height at which this htlc expires.
|
||||||
|
int32 expiry_height = 7 [json_name = "expiry_height"];
|
||||||
|
|
||||||
|
/// Current state the htlc is in.
|
||||||
|
InvoiceHTLCState state = 8 [json_name = "state"];
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddInvoiceResponse {
|
message AddInvoiceResponse {
|
||||||
|
|||||||
@@ -2489,9 +2489,70 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"$ref": "#/definitions/InvoiceInvoiceState",
|
"$ref": "#/definitions/InvoiceInvoiceState",
|
||||||
"description": "*\nThe state the invoice is in."
|
"description": "*\nThe state the invoice is in."
|
||||||
|
},
|
||||||
|
"htlcs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/lnrpcInvoiceHTLC"
|
||||||
|
},
|
||||||
|
"description": "/ List of HTLCs paying to this invoice [EXPERIMENTAL]."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcInvoiceHTLC": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"chan_id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "/ Short channel id over which the htlc was received."
|
||||||
|
},
|
||||||
|
"htlc_index": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "/ Index identifying the htlc on the channel."
|
||||||
|
},
|
||||||
|
"amt_msat": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "/ The amount of the htlc in msat."
|
||||||
|
},
|
||||||
|
"accept_height": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "/ Block height at which this htlc was accepted."
|
||||||
|
},
|
||||||
|
"accept_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "/ Time at which this htlc was accepted."
|
||||||
|
},
|
||||||
|
"resolve_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "/ Time at which this htlc was settled or cancelled."
|
||||||
|
},
|
||||||
|
"expiry_height": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"description": "/ Block height at which this htlc expires."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"$ref": "#/definitions/lnrpcInvoiceHTLCState",
|
||||||
|
"description": "/ Current state the htlc is in."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "/ Details of an HTLC that paid to an invoice"
|
||||||
|
},
|
||||||
|
"lnrpcInvoiceHTLCState": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ACCEPTED",
|
||||||
|
"SETTLED",
|
||||||
|
"CANCELLED"
|
||||||
|
],
|
||||||
|
"default": "ACCEPTED"
|
||||||
|
},
|
||||||
"lnrpcLightningAddress": {
|
"lnrpcLightningAddress": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+1
-1
@@ -3496,7 +3496,7 @@ func (r *rpcServer) LookupInvoice(ctx context.Context,
|
|||||||
|
|
||||||
rpcsLog.Tracef("[lookupinvoice] searching for invoice %x", payHash[:])
|
rpcsLog.Tracef("[lookupinvoice] searching for invoice %x", payHash[:])
|
||||||
|
|
||||||
invoice, _, err := r.server.invoices.LookupInvoice(payHash)
|
invoice, err := r.server.invoices.LookupInvoice(payHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/watchtower/wtclient"
|
"github.com/lightningnetwork/lnd/watchtower/wtclient"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||||
"github.com/lightningnetwork/lnd/zpay32"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -347,14 +346,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
|||||||
readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout,
|
readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
decodeFinalCltvExpiry := func(payReq string) (uint32, error) {
|
|
||||||
invoice, err := zpay32.Decode(payReq, activeNetParams.Params)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint32(invoice.MinFinalCLTVExpiry()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &server{
|
s := &server{
|
||||||
chanDB: chanDB,
|
chanDB: chanDB,
|
||||||
cc: cc,
|
cc: cc,
|
||||||
@@ -364,8 +355,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
|
|||||||
chansToRestore: chansToRestore,
|
chansToRestore: chansToRestore,
|
||||||
|
|
||||||
invoices: invoices.NewRegistry(
|
invoices: invoices.NewRegistry(
|
||||||
chanDB, decodeFinalCltvExpiry,
|
chanDB, defaultFinalCltvRejectDelta,
|
||||||
defaultFinalCltvRejectDelta,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
channelNotifier: channelnotifier.New(chanDB),
|
channelNotifier: channelnotifier.New(chanDB),
|
||||||
|
|||||||
Reference in new issue
Block a user