multi: break invoice depenency on channeldb
Now that we have the new package `lnd/channeldb/models` we can invert the depenency between `channeldb` and `invoices`. - Move all the invoice related types and errors to the `invoices` package. - Ensure that all the packages dealing with invoices use the types and interfaces defined in the `invoices` package. - Implement the InvoiceDB interface (defined in `lnd/invoices`) in channeldb. - Add new mock for InterfaceDB. - `InvoiceRegistery` tests are now in its own subpacakge (they need to import both invoices & channeldb). This is temporary until we can decouple them.
This commit is contained in:
26 files changed
+2839
-2253
No files matched your search
@@ -0,0 +1,108 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvoiceAlreadySettled is returned when the invoice is already
|
||||
// settled.
|
||||
ErrInvoiceAlreadySettled = errors.New("invoice already settled")
|
||||
|
||||
// ErrInvoiceAlreadyCanceled is returned when the invoice is already
|
||||
// canceled.
|
||||
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled")
|
||||
|
||||
// ErrInvoiceAlreadyAccepted is returned when the invoice is already
|
||||
// accepted.
|
||||
ErrInvoiceAlreadyAccepted = errors.New("invoice already accepted")
|
||||
|
||||
// ErrInvoiceStillOpen is returned when the invoice is still open.
|
||||
ErrInvoiceStillOpen = errors.New("invoice still open")
|
||||
|
||||
// ErrInvoiceCannotOpen is returned when an attempt is made to move an
|
||||
// invoice to the open state.
|
||||
ErrInvoiceCannotOpen = errors.New("cannot move invoice to open")
|
||||
|
||||
// ErrInvoiceCannotAccept is returned when an attempt is made to accept
|
||||
// an invoice while the invoice is not in the open state.
|
||||
ErrInvoiceCannotAccept = errors.New("cannot accept invoice")
|
||||
|
||||
// ErrInvoicePreimageMismatch is returned when the preimage doesn't
|
||||
// match the invoice hash.
|
||||
ErrInvoicePreimageMismatch = errors.New("preimage does not match")
|
||||
|
||||
// ErrHTLCPreimageMissing is returned when trying to accept/settle an
|
||||
// AMP HTLC but the HTLC-level preimage has not been set.
|
||||
ErrHTLCPreimageMissing = errors.New("AMP htlc missing preimage")
|
||||
|
||||
// ErrHTLCPreimageMismatch is returned when trying to accept/settle an
|
||||
// AMP HTLC but the HTLC-level preimage does not satisfying the
|
||||
// HTLC-level payment hash.
|
||||
ErrHTLCPreimageMismatch = errors.New("htlc preimage mismatch")
|
||||
|
||||
// ErrHTLCAlreadySettled is returned when trying to settle an invoice
|
||||
// but HTLC already exists in the settled state.
|
||||
ErrHTLCAlreadySettled = errors.New("htlc already settled")
|
||||
|
||||
// ErrInvoiceHasHtlcs is returned when attempting to insert an invoice
|
||||
// that already has HTLCs.
|
||||
ErrInvoiceHasHtlcs = errors.New("cannot add invoice with htlcs")
|
||||
|
||||
// ErrEmptyHTLCSet is returned when attempting to accept or settle and
|
||||
// HTLC set that has no HTLCs.
|
||||
ErrEmptyHTLCSet = errors.New("cannot settle/accept empty HTLC set")
|
||||
|
||||
// ErrUnexpectedInvoicePreimage is returned when an invoice-level
|
||||
// preimage is provided when trying to settle an invoice that shouldn't
|
||||
// have one, e.g. an AMP invoice.
|
||||
ErrUnexpectedInvoicePreimage = errors.New(
|
||||
"unexpected invoice preimage provided on settle",
|
||||
)
|
||||
|
||||
// ErrHTLCPreimageAlreadyExists is returned when trying to set an
|
||||
// htlc-level preimage but one is already known.
|
||||
ErrHTLCPreimageAlreadyExists = errors.New(
|
||||
"htlc-level preimage already exists",
|
||||
)
|
||||
|
||||
// ErrInvoiceNotFound is returned when a targeted invoice can't be
|
||||
// found.
|
||||
ErrInvoiceNotFound = errors.New("unable to locate invoice")
|
||||
|
||||
// ErrNoInvoicesCreated is returned when we don't have invoices in
|
||||
// our database to return.
|
||||
ErrNoInvoicesCreated = errors.New("there are no existing invoices")
|
||||
|
||||
// ErrDuplicateInvoice is returned when an invoice with the target
|
||||
// payment hash already exists.
|
||||
ErrDuplicateInvoice = errors.New(
|
||||
"invoice with payment hash already exists",
|
||||
)
|
||||
|
||||
// ErrDuplicatePayAddr is returned when an invoice with the target
|
||||
// payment addr already exists.
|
||||
ErrDuplicatePayAddr = errors.New(
|
||||
"invoice with payemnt addr already exists",
|
||||
)
|
||||
|
||||
// ErrInvRefEquivocation is returned when an InvoiceRef targets
|
||||
// multiple, distinct invoices.
|
||||
ErrInvRefEquivocation = errors.New("inv ref matches multiple invoices")
|
||||
|
||||
// ErrNoPaymentsCreated is returned when bucket of payments hasn't been
|
||||
// created.
|
||||
ErrNoPaymentsCreated = errors.New("there are no existing payments")
|
||||
)
|
||||
|
||||
// ErrDuplicateSetID is an error returned when attempting to adding an AMP HTLC
|
||||
// to an invoice, but another invoice is already indexed by the same set id.
|
||||
type ErrDuplicateSetID struct {
|
||||
SetID [32]byte
|
||||
}
|
||||
|
||||
// Error returns a human-readable description of ErrDuplicateSetID.
|
||||
func (e ErrDuplicateSetID) Error() string {
|
||||
return fmt.Sprintf("invoice with set_id=%x already exists", e.SetID)
|
||||
}
|
||||
@@ -1,9 +1,91 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
)
|
||||
|
||||
// InvScanFunc is a helper type used to specify the type used in the
|
||||
// ScanInvoices methods (part of the InvoiceDB interface).
|
||||
type InvScanFunc func(lntypes.Hash, *Invoice) error
|
||||
|
||||
// InvoiceDB is the database that stores the information about invoices.
|
||||
type InvoiceDB interface {
|
||||
// AddInvoice inserts the targeted invoice into the database.
|
||||
// If the invoice has *any* payment hashes which already exists within
|
||||
// the database, then the insertion will be aborted and rejected due to
|
||||
// the strict policy banning any duplicate payment hashes.
|
||||
//
|
||||
// NOTE: A side effect of this function is that it sets AddIndex on
|
||||
// newInvoice.
|
||||
AddInvoice(invoice *Invoice, paymentHash lntypes.Hash) (uint64, error)
|
||||
|
||||
// InvoicesAddedSince can be used by callers to seek into the event
|
||||
// time series of all the invoices added in the database. The specified
|
||||
// sinceAddIndex should be the highest add index that the caller knows
|
||||
// of. This method will return all invoices with an add index greater
|
||||
// than the specified sinceAddIndex.
|
||||
//
|
||||
// NOTE: The index starts from 1, as a result. We enforce that
|
||||
// specifying a value below the starting index value is a noop.
|
||||
InvoicesAddedSince(sinceAddIndex uint64) ([]Invoice, error)
|
||||
|
||||
// LookupInvoice attempts to look up an invoice according to its 32 byte
|
||||
// payment hash. If an invoice which can settle the HTLC identified by
|
||||
// the passed payment hash isn't found, then an error is returned.
|
||||
// Otherwise, the full invoice is returned.
|
||||
// Before setting the incoming HTLC, the values SHOULD be checked to
|
||||
// ensure the payer meets the agreed upon contractual terms of the
|
||||
// payment.
|
||||
LookupInvoice(ref InvoiceRef) (Invoice, error)
|
||||
|
||||
// ScanInvoices scans through all invoices and calls the passed scanFunc
|
||||
// for each invoice with its respective payment hash. Additionally a
|
||||
// reset() closure is passed which is used to reset/initialize partial
|
||||
// results and also to signal if the kvdb.View transaction has been
|
||||
// retried.
|
||||
//
|
||||
// TODO(positiveblue): abstract this functionality so it makes sense for
|
||||
// other backends like sql.
|
||||
ScanInvoices(scanFunc InvScanFunc, reset func()) error
|
||||
|
||||
// QueryInvoices allows a caller to query the invoice database for
|
||||
// invoices within the specified add index range.
|
||||
QueryInvoices(q InvoiceQuery) (InvoiceSlice, error)
|
||||
|
||||
// UpdateInvoice attempts to update an invoice corresponding to the
|
||||
// passed payment hash. If an invoice matching the passed payment hash
|
||||
// doesn't exist within the database, then the action will fail with a
|
||||
// "not found" error.
|
||||
//
|
||||
// The update is performed inside the same database transaction that
|
||||
// fetches the invoice and is therefore atomic. The fields to update
|
||||
// are controlled by the supplied callback.
|
||||
//
|
||||
// TODO(positiveblue): abstract this functionality so it makes sense for
|
||||
// other backends like sql.
|
||||
UpdateInvoice(ref InvoiceRef, setIDHint *SetID,
|
||||
callback InvoiceUpdateCallback) (*Invoice, error)
|
||||
|
||||
// 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 settled invoice that have a settle index higher than
|
||||
// the passed sinceSettleIndex.
|
||||
//
|
||||
// NOTE: The index starts from 1, as a result. We enforce that
|
||||
// specifying a value below the starting index value is a noop.
|
||||
InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error)
|
||||
|
||||
// DeleteInvoice attempts to delete the passed invoices from the
|
||||
// database in one transaction. The passed delete references hold all
|
||||
// keys required to delete the invoices without also needing to
|
||||
// deserialze them.
|
||||
DeleteInvoice(invoicesToDelete []InvoiceDeleteRef) error
|
||||
}
|
||||
|
||||
// Payload abstracts access to any additional fields provided in the final hop's
|
||||
// TLV onion payload.
|
||||
type Payload interface {
|
||||
@@ -23,3 +105,61 @@ type Payload interface {
|
||||
// payment to the payee.
|
||||
Metadata() []byte
|
||||
}
|
||||
|
||||
// InvoiceQuery represents a query to the invoice database. The query allows a
|
||||
// caller to retrieve all invoices starting from a particular add index and
|
||||
// limit the number of results returned.
|
||||
type InvoiceQuery struct {
|
||||
// IndexOffset is the offset within the add indices to start at. This
|
||||
// can be used to start the response at a particular invoice.
|
||||
IndexOffset uint64
|
||||
|
||||
// NumMaxInvoices is the maximum number of invoices that should be
|
||||
// starting from the add index.
|
||||
NumMaxInvoices uint64
|
||||
|
||||
// PendingOnly, if set, returns unsettled invoices starting from the
|
||||
// add index.
|
||||
PendingOnly bool
|
||||
|
||||
// Reversed, if set, indicates that the invoices returned should start
|
||||
// from the IndexOffset and go backwards.
|
||||
Reversed bool
|
||||
|
||||
// CreationDateStart, if set, filters out all invoices with a creation
|
||||
// date greater than or euqal to it.
|
||||
CreationDateStart time.Time
|
||||
|
||||
// CreationDateEnd, if set, filters out all invoices with a creation
|
||||
// date less than or euqal to it.
|
||||
CreationDateEnd time.Time
|
||||
}
|
||||
|
||||
// InvoiceSlice is the response to a invoice query. It includes the original
|
||||
// query, the set of invoices that match the query, and an integer which
|
||||
// represents the offset index of the last item in the set of returned invoices.
|
||||
// This integer allows callers to resume their query using this offset in the
|
||||
// event that the query's response exceeds the maximum number of returnable
|
||||
// invoices.
|
||||
type InvoiceSlice struct {
|
||||
InvoiceQuery
|
||||
|
||||
// Invoices is the set of invoices that matched the query above.
|
||||
Invoices []Invoice
|
||||
|
||||
// FirstIndexOffset is the index of the first element in the set of
|
||||
// returned Invoices above. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response.
|
||||
FirstIndexOffset uint64
|
||||
|
||||
// LastIndexOffset is the index of the last element in the set of
|
||||
// returned Invoices above. Callers can use this to resume their query
|
||||
// in the event that the slice has too many events to fit into a single
|
||||
// response.
|
||||
LastIndexOffset uint64
|
||||
}
|
||||
|
||||
// CircuitKey is a tuple of channel ID and HTLC ID, used to uniquely identify
|
||||
// HTLCs in a circuit.
|
||||
type CircuitKey = models.CircuitKey
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
@@ -178,18 +177,18 @@ func (ew *InvoiceExpiryWatcher) Stop() {
|
||||
// makeInvoiceExpiry checks if the passed invoice may be canceled and calculates
|
||||
// the expiry time and creates a slimmer invoiceExpiry implementation.
|
||||
func makeInvoiceExpiry(paymentHash lntypes.Hash,
|
||||
invoice *channeldb.Invoice) invoiceExpiry {
|
||||
invoice *Invoice) invoiceExpiry {
|
||||
|
||||
switch invoice.State {
|
||||
// If we have an open invoice with no htlcs, we want to expire the
|
||||
// invoice based on timestamp
|
||||
case channeldb.ContractOpen:
|
||||
case ContractOpen:
|
||||
return makeTimestampExpiry(paymentHash, invoice)
|
||||
|
||||
// If an invoice has active htlcs, we want to expire it based on block
|
||||
// height. We only do this for hodl invoices, since regular invoices
|
||||
// should resolve themselves automatically.
|
||||
case channeldb.ContractAccepted:
|
||||
case ContractAccepted:
|
||||
if !invoice.HodlInvoice {
|
||||
log.Debugf("Invoice in accepted state not added to "+
|
||||
"expiry watcher: %v", paymentHash)
|
||||
@@ -201,7 +200,7 @@ func makeInvoiceExpiry(paymentHash lntypes.Hash,
|
||||
for _, htlc := range invoice.Htlcs {
|
||||
// We only care about accepted htlcs, since they will
|
||||
// trigger force-closes.
|
||||
if htlc.State != channeldb.HtlcStateAccepted {
|
||||
if htlc.State != HtlcStateAccepted {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -222,9 +221,9 @@ func makeInvoiceExpiry(paymentHash lntypes.Hash,
|
||||
|
||||
// makeTimestampExpiry creates a timestamp-based expiry entry.
|
||||
func makeTimestampExpiry(paymentHash lntypes.Hash,
|
||||
invoice *channeldb.Invoice) *invoiceExpiryTs {
|
||||
invoice *Invoice) *invoiceExpiryTs {
|
||||
|
||||
if invoice.State != channeldb.ContractOpen {
|
||||
if invoice.State != ContractOpen {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -349,11 +348,11 @@ func (ew *InvoiceExpiryWatcher) expireInvoice(hash lntypes.Hash, force bool) {
|
||||
switch err {
|
||||
case nil:
|
||||
|
||||
case channeldb.ErrInvoiceAlreadyCanceled:
|
||||
case ErrInvoiceAlreadyCanceled:
|
||||
|
||||
case channeldb.ErrInvoiceAlreadySettled:
|
||||
case ErrInvoiceAlreadySettled:
|
||||
|
||||
case channeldb.ErrInvoiceNotFound:
|
||||
case ErrInvoiceNotFound:
|
||||
// It's possible that the user has manually canceled the invoice
|
||||
// which will then be deleted by the garbage collector resulting
|
||||
// in an ErrInvoiceNotFound error.
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -22,29 +20,6 @@ type invoiceExpiryWatcherTest struct {
|
||||
canceledInvoices []lntypes.Hash
|
||||
}
|
||||
|
||||
type mockChainNotifier struct {
|
||||
chainntnfs.ChainNotifier
|
||||
|
||||
blockChan chan *chainntnfs.BlockEpoch
|
||||
}
|
||||
|
||||
func newMockNotifier() *mockChainNotifier {
|
||||
return &mockChainNotifier{
|
||||
blockChan: make(chan *chainntnfs.BlockEpoch),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn mocks a block epoch notification, using the mock's
|
||||
// block channel to deliver blocks to the client.
|
||||
func (m *mockChainNotifier) RegisterBlockEpochNtfn(*chainntnfs.BlockEpoch) (
|
||||
*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
return &chainntnfs.BlockEpochEvent{
|
||||
Epochs: m.blockChan,
|
||||
Cancel: func() {},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newInvoiceExpiryWatcherTest creates a new InvoiceExpiryWatcher test fixture
|
||||
// and sets up the test environment.
|
||||
func newInvoiceExpiryWatcherTest(t *testing.T, now time.Time,
|
||||
@@ -213,7 +188,7 @@ func TestExpiredHodlInv(t *testing.T) {
|
||||
expiry := time.Hour
|
||||
|
||||
test := setupHodlExpiry(
|
||||
t, creationDate, expiry, 0, channeldb.ContractOpen, nil,
|
||||
t, creationDate, expiry, 0, ContractOpen, nil,
|
||||
)
|
||||
|
||||
test.assertCanceled(t, test.hash)
|
||||
@@ -231,7 +206,7 @@ func TestAcceptedHodlNotExpired(t *testing.T) {
|
||||
expiry := time.Hour
|
||||
|
||||
test := setupHodlExpiry(
|
||||
t, creationDate, expiry, 0, channeldb.ContractAccepted, nil,
|
||||
t, creationDate, expiry, 0, ContractAccepted, nil,
|
||||
)
|
||||
defer test.watcher.Stop()
|
||||
|
||||
@@ -255,15 +230,15 @@ func TestAcceptedHodlNotExpired(t *testing.T) {
|
||||
func TestHeightAlreadyExpired(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expiredHtlc := []*channeldb.InvoiceHTLC{
|
||||
expiredHtlc := []*InvoiceHTLC{
|
||||
{
|
||||
State: channeldb.HtlcStateAccepted,
|
||||
State: HtlcStateAccepted,
|
||||
Expiry: uint32(testCurrentHeight),
|
||||
},
|
||||
}
|
||||
|
||||
test := setupHodlExpiry(
|
||||
t, testTime, time.Hour, 0, channeldb.ContractAccepted,
|
||||
t, testTime, time.Hour, 0, ContractAccepted,
|
||||
expiredHtlc,
|
||||
)
|
||||
defer test.watcher.Stop()
|
||||
@@ -284,7 +259,7 @@ func TestExpiryHeightArrives(t *testing.T) {
|
||||
|
||||
// Start out with a hodl invoice that is open, and has no htlcs.
|
||||
test := setupHodlExpiry(
|
||||
t, creationDate, expiry, delta, channeldb.ContractOpen, nil,
|
||||
t, creationDate, expiry, delta, ContractOpen, nil,
|
||||
)
|
||||
defer test.watcher.Stop()
|
||||
|
||||
@@ -293,7 +268,7 @@ func TestExpiryHeightArrives(t *testing.T) {
|
||||
|
||||
// Add htlcs to our invoice and progress its state to accepted.
|
||||
test.watcher.AddInvoices(expiry1)
|
||||
test.setState(channeldb.ContractAccepted)
|
||||
test.setState(ContractAccepted)
|
||||
|
||||
// Progress time so that our expiry has elapsed. We no longer expect
|
||||
// this invoice to be canceled because it has been accepted.
|
||||
|
||||
+172
-165
@@ -7,8 +7,6 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
@@ -17,13 +15,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvoiceExpiryTooSoon is returned when an invoice is attempted to be
|
||||
// accepted or settled with not enough blocks remaining.
|
||||
// ErrInvoiceExpiryTooSoon is returned when an invoice is attempted to
|
||||
// be accepted or settled with not enough blocks remaining.
|
||||
ErrInvoiceExpiryTooSoon = errors.New("invoice expiry too soon")
|
||||
|
||||
// ErrInvoiceAmountTooLow is returned when an invoice is attempted to be
|
||||
// accepted or settled with an amount that is too low.
|
||||
ErrInvoiceAmountTooLow = errors.New("paid amount less than invoice amount")
|
||||
// ErrInvoiceAmountTooLow is returned when an invoice is attempted to
|
||||
// be accepted or settled with an amount that is too low.
|
||||
ErrInvoiceAmountTooLow = errors.New(
|
||||
"paid amount less than invoice amount",
|
||||
)
|
||||
|
||||
// ErrShuttingDown is returned when an operation failed because the
|
||||
// invoice registry is shutting down.
|
||||
@@ -79,10 +79,10 @@ type RegistryConfig struct {
|
||||
// mpp htlcs for which the complete set didn't arrive in time.
|
||||
type htlcReleaseEvent struct {
|
||||
// invoiceRef identifiers the invoice this htlc belongs to.
|
||||
invoiceRef channeldb.InvoiceRef
|
||||
invoiceRef InvoiceRef
|
||||
|
||||
// key is the circuit key of the htlc to release.
|
||||
key models.CircuitKey
|
||||
key CircuitKey
|
||||
|
||||
// releaseTime is the time at which to release the htlc.
|
||||
releaseTime time.Time
|
||||
@@ -104,7 +104,7 @@ type InvoiceRegistry struct {
|
||||
|
||||
nextClientID uint32 // must be used atomically
|
||||
|
||||
cdb *channeldb.DB
|
||||
idb InvoiceDB
|
||||
|
||||
// cfg contains the registry's configuration parameters.
|
||||
cfg *RegistryConfig
|
||||
@@ -130,13 +130,14 @@ type InvoiceRegistry struct {
|
||||
// necessary to avoid deadlocks in the registry when processing invoice
|
||||
// events.
|
||||
hodlSubscriptionsMux sync.RWMutex
|
||||
// subscriptions is a map from a circuit key to a list of subscribers.
|
||||
// It is used for efficient notification of links.
|
||||
hodlSubscriptions map[models.CircuitKey]map[chan<- interface{}]struct{}
|
||||
|
||||
// hodlSubscriptions is a map from a circuit key to a list of
|
||||
// subscribers. It is used for efficient notification of links.
|
||||
hodlSubscriptions map[CircuitKey]map[chan<- interface{}]struct{}
|
||||
|
||||
// reverseSubscriptions tracks circuit keys subscribed to per
|
||||
// subscriber. This is used to unsubscribe from all hashes efficiently.
|
||||
hodlReverseSubscriptions map[chan<- interface{}]map[models.CircuitKey]struct{}
|
||||
hodlReverseSubscriptions map[chan<- interface{}]map[CircuitKey]struct{}
|
||||
|
||||
// htlcAutoReleaseChan contains the new htlcs that need to be
|
||||
// auto-released.
|
||||
@@ -152,19 +153,21 @@ type InvoiceRegistry struct {
|
||||
// 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
|
||||
// which are volatile yet available system wide within the daemon.
|
||||
func NewRegistry(cdb *channeldb.DB, expiryWatcher *InvoiceExpiryWatcher,
|
||||
func NewRegistry(idb InvoiceDB, expiryWatcher *InvoiceExpiryWatcher,
|
||||
cfg *RegistryConfig) *InvoiceRegistry {
|
||||
|
||||
notificationClients := make(map[uint32]*InvoiceSubscription)
|
||||
singleNotificationClients := make(map[uint32]*SingleInvoiceSubscription)
|
||||
return &InvoiceRegistry{
|
||||
cdb: cdb,
|
||||
notificationClients: make(map[uint32]*InvoiceSubscription),
|
||||
singleNotificationClients: make(map[uint32]*SingleInvoiceSubscription),
|
||||
idb: idb,
|
||||
notificationClients: notificationClients,
|
||||
singleNotificationClients: singleNotificationClients,
|
||||
invoiceEvents: make(chan *invoiceEvent, 100),
|
||||
hodlSubscriptions: make(
|
||||
map[models.CircuitKey]map[chan<- interface{}]struct{},
|
||||
map[CircuitKey]map[chan<- interface{}]struct{},
|
||||
),
|
||||
hodlReverseSubscriptions: make(
|
||||
map[chan<- interface{}]map[models.CircuitKey]struct{},
|
||||
map[chan<- interface{}]map[CircuitKey]struct{},
|
||||
),
|
||||
cfg: cfg,
|
||||
htlcAutoReleaseChan: make(chan *htlcReleaseEvent),
|
||||
@@ -179,7 +182,7 @@ func NewRegistry(cdb *channeldb.DB, expiryWatcher *InvoiceExpiryWatcher,
|
||||
func (i *InvoiceRegistry) scanInvoicesOnStart() error {
|
||||
var (
|
||||
pending []invoiceExpiry
|
||||
removable []channeldb.InvoiceDeleteRef
|
||||
removable []InvoiceDeleteRef
|
||||
)
|
||||
|
||||
reset := func() {
|
||||
@@ -189,11 +192,11 @@ func (i *InvoiceRegistry) scanInvoicesOnStart() error {
|
||||
// using the etcd driver, where all transactions are allowed
|
||||
// to retry for serializability).
|
||||
pending = nil
|
||||
removable = make([]channeldb.InvoiceDeleteRef, 0)
|
||||
removable = make([]InvoiceDeleteRef, 0)
|
||||
}
|
||||
|
||||
scanFunc := func(paymentHash lntypes.Hash,
|
||||
invoice *channeldb.Invoice) error {
|
||||
invoice *Invoice) error {
|
||||
|
||||
if invoice.IsPending() {
|
||||
expiryRef := makeInvoiceExpiry(paymentHash, invoice)
|
||||
@@ -201,19 +204,19 @@ func (i *InvoiceRegistry) scanInvoicesOnStart() error {
|
||||
pending = append(pending, expiryRef)
|
||||
}
|
||||
} else if i.cfg.GcCanceledInvoicesOnStartup &&
|
||||
invoice.State == channeldb.ContractCanceled {
|
||||
invoice.State == ContractCanceled {
|
||||
|
||||
// Consider invoice for removal if it is already
|
||||
// canceled. Invoices that are expired but not yet
|
||||
// canceled, will be queued up for cancellation after
|
||||
// startup and will be deleted afterwards.
|
||||
ref := channeldb.InvoiceDeleteRef{
|
||||
ref := InvoiceDeleteRef{
|
||||
PayHash: paymentHash,
|
||||
AddIndex: invoice.AddIndex,
|
||||
SettleIndex: invoice.SettleIndex,
|
||||
}
|
||||
|
||||
if invoice.Terms.PaymentAddr != channeldb.BlankPayAddr {
|
||||
if invoice.Terms.PaymentAddr != BlankPayAddr {
|
||||
ref.PayAddr = &invoice.Terms.PaymentAddr
|
||||
}
|
||||
|
||||
@@ -222,7 +225,7 @@ func (i *InvoiceRegistry) scanInvoicesOnStart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := i.cdb.ScanInvoices(scanFunc, reset)
|
||||
err := i.idb.ScanInvoices(scanFunc, reset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -234,7 +237,7 @@ func (i *InvoiceRegistry) scanInvoicesOnStart() error {
|
||||
if len(removable) > 0 {
|
||||
log.Infof("Attempting to delete %v canceled invoices",
|
||||
len(removable))
|
||||
if err := i.cdb.DeleteInvoice(removable); err != nil {
|
||||
if err := i.idb.DeleteInvoice(removable); err != nil {
|
||||
log.Warnf("Deleting canceled invoices failed: %v", err)
|
||||
} else {
|
||||
log.Infof("Deleted %v canceled invoices",
|
||||
@@ -287,7 +290,7 @@ func (i *InvoiceRegistry) Stop() error {
|
||||
// instance where invoices are settled.
|
||||
type invoiceEvent struct {
|
||||
hash lntypes.Hash
|
||||
invoice *channeldb.Invoice
|
||||
invoice *Invoice
|
||||
setID *[32]byte
|
||||
}
|
||||
|
||||
@@ -323,8 +326,8 @@ func (i *InvoiceRegistry) invoiceEventLoop() {
|
||||
// For backwards compatibility, do not notify all
|
||||
// invoice subscribers of cancel and accept events.
|
||||
state := event.invoice.State
|
||||
if state != channeldb.ContractCanceled &&
|
||||
state != channeldb.ContractAccepted {
|
||||
if state != ContractCanceled &&
|
||||
state != ContractAccepted {
|
||||
|
||||
i.dispatchToClients(event)
|
||||
}
|
||||
@@ -401,7 +404,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
switch {
|
||||
// If we've already sent this settle event to
|
||||
// the client, then we can skip this.
|
||||
case state == channeldb.ContractSettled &&
|
||||
case state == ContractSettled &&
|
||||
client.settleIndex >= invoice.SettleIndex:
|
||||
continue
|
||||
|
||||
@@ -409,14 +412,14 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
// the client then we can skip this one, but only if this isn't
|
||||
// an AMP invoice. AMP invoices always remain in the settle
|
||||
// state as a base invoice.
|
||||
case event.setID == nil && state == channeldb.ContractOpen &&
|
||||
case event.setID == nil && state == ContractOpen &&
|
||||
client.addIndex >= invoice.AddIndex:
|
||||
continue
|
||||
|
||||
// These two states should never happen, but we
|
||||
// log them just in case so we can detect this
|
||||
// instance.
|
||||
case state == channeldb.ContractOpen &&
|
||||
case state == ContractOpen &&
|
||||
client.addIndex+1 != invoice.AddIndex:
|
||||
log.Warnf("client=%v for invoice "+
|
||||
"notifications missed an update, "+
|
||||
@@ -424,7 +427,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
clientID, client.addIndex,
|
||||
invoice.AddIndex)
|
||||
|
||||
case state == channeldb.ContractSettled &&
|
||||
case state == ContractSettled &&
|
||||
client.settleIndex+1 != invoice.SettleIndex:
|
||||
log.Warnf("client=%v for invoice "+
|
||||
"notifications missed an update, "+
|
||||
@@ -456,10 +459,10 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
// event is added while we're catching up a new client.
|
||||
invState := event.invoice.State
|
||||
switch {
|
||||
case invState == channeldb.ContractSettled:
|
||||
case invState == ContractSettled:
|
||||
client.settleIndex = invoice.SettleIndex
|
||||
|
||||
case invState == channeldb.ContractOpen && event.setID == nil:
|
||||
case invState == ContractOpen && event.setID == nil:
|
||||
client.addIndex = invoice.AddIndex
|
||||
|
||||
// If this is an AMP invoice, then we'll need to use the set ID
|
||||
@@ -467,7 +470,7 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
// invoices never go to the open state, but if a setID is
|
||||
// passed, then we know it was just settled and will track the
|
||||
// highest settle index so far.
|
||||
case invState == channeldb.ContractOpen && event.setID != nil:
|
||||
case invState == ContractOpen && event.setID != nil:
|
||||
setID := *event.setID
|
||||
client.settleIndex = invoice.AMPState[setID].SettleIndex
|
||||
|
||||
@@ -480,13 +483,15 @@ func (i *InvoiceRegistry) dispatchToClients(event *invoiceEvent) {
|
||||
|
||||
// deliverBacklogEvents will attempts to query the invoice database for any
|
||||
// notifications that the client has missed since it reconnected last.
|
||||
func (i *InvoiceRegistry) deliverBacklogEvents(client *InvoiceSubscription) error {
|
||||
addEvents, err := i.cdb.InvoicesAddedSince(client.addIndex)
|
||||
func (i *InvoiceRegistry) deliverBacklogEvents(
|
||||
client *InvoiceSubscription) error {
|
||||
|
||||
addEvents, err := i.idb.InvoicesAddedSince(client.addIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settleEvents, err := i.cdb.InvoicesSettledSince(client.settleIndex)
|
||||
settleEvents, err := i.idb.InvoicesSettledSince(client.settleIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -533,13 +538,13 @@ func (i *InvoiceRegistry) deliverBacklogEvents(client *InvoiceSubscription) erro
|
||||
func (i *InvoiceRegistry) deliverSingleBacklogEvents(
|
||||
client *SingleInvoiceSubscription) error {
|
||||
|
||||
invoice, err := i.cdb.LookupInvoice(client.invoiceRef)
|
||||
invoice, err := i.idb.LookupInvoice(client.invoiceRef)
|
||||
|
||||
// It is possible that the invoice does not exist yet, but the client is
|
||||
// already watching it in anticipation.
|
||||
if err == channeldb.ErrInvoiceNotFound ||
|
||||
err == channeldb.ErrNoInvoicesCreated {
|
||||
|
||||
isNotFound := errors.Is(err, ErrInvoiceNotFound)
|
||||
isNotCreated := errors.Is(err, ErrNoInvoicesCreated)
|
||||
if isNotFound || isNotCreated {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -573,15 +578,15 @@ func (i *InvoiceRegistry) deliverSingleBacklogEvents(
|
||||
// addIndex of the newly created invoice which monotonically increases for each
|
||||
// new invoice added. A side effect of this function is that it also sets
|
||||
// AddIndex on the invoice argument.
|
||||
func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
|
||||
func (i *InvoiceRegistry) AddInvoice(invoice *Invoice,
|
||||
paymentHash lntypes.Hash) (uint64, error) {
|
||||
|
||||
i.Lock()
|
||||
|
||||
ref := channeldb.InvoiceRefByHash(paymentHash)
|
||||
ref := InvoiceRefByHash(paymentHash)
|
||||
log.Debugf("Invoice%v: added with terms %v", ref, invoice.Terms)
|
||||
|
||||
addIndex, err := i.cdb.AddInvoice(invoice, paymentHash)
|
||||
addIndex, err := i.idb.AddInvoice(invoice, paymentHash)
|
||||
if err != nil {
|
||||
i.Unlock()
|
||||
return 0, err
|
||||
@@ -607,27 +612,23 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice,
|
||||
// then we're able to pull the funds pending within an HTLC.
|
||||
//
|
||||
// TODO(roasbeef): ignore if settled?
|
||||
func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice,
|
||||
error) {
|
||||
|
||||
func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (Invoice, error) {
|
||||
// We'll check the database to see if there's an existing matching
|
||||
// invoice.
|
||||
ref := channeldb.InvoiceRefByHash(rHash)
|
||||
return i.cdb.LookupInvoice(ref)
|
||||
ref := InvoiceRefByHash(rHash)
|
||||
return i.idb.LookupInvoice(ref)
|
||||
}
|
||||
|
||||
// LookupInvoiceByRef looks up an invoice by the given reference, if found
|
||||
// then we're able to pull the funds pending within an HTLC.
|
||||
func (i *InvoiceRegistry) LookupInvoiceByRef(
|
||||
ref channeldb.InvoiceRef) (channeldb.Invoice, error) {
|
||||
|
||||
return i.cdb.LookupInvoice(ref)
|
||||
func (i *InvoiceRegistry) LookupInvoiceByRef(ref InvoiceRef) (Invoice, error) {
|
||||
return i.idb.LookupInvoice(ref)
|
||||
}
|
||||
|
||||
// startHtlcTimer starts a new timer via the invoice registry main loop that
|
||||
// cancels a single htlc on an invoice when the htlc hold duration has passed.
|
||||
func (i *InvoiceRegistry) startHtlcTimer(invoiceRef channeldb.InvoiceRef,
|
||||
key models.CircuitKey, acceptTime time.Time) error {
|
||||
func (i *InvoiceRegistry) startHtlcTimer(invoiceRef InvoiceRef,
|
||||
key CircuitKey, acceptTime time.Time) error {
|
||||
|
||||
releaseTime := acceptTime.Add(i.cfg.HtlcHoldDuration)
|
||||
event := &htlcReleaseEvent{
|
||||
@@ -648,14 +649,12 @@ func (i *InvoiceRegistry) startHtlcTimer(invoiceRef channeldb.InvoiceRef,
|
||||
// cancelSingleHtlc cancels a single accepted htlc on an invoice. It takes
|
||||
// a resolution result which will be used to notify subscribed links and
|
||||
// resolvers of the details of the htlc cancellation.
|
||||
func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
key models.CircuitKey, result FailResolutionResult) error {
|
||||
|
||||
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef InvoiceRef,
|
||||
key CircuitKey, result FailResolutionResult) error {
|
||||
|
||||
updateInvoice := func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
// Only allow individual htlc cancellation on open invoices.
|
||||
if invoice.State != channeldb.ContractOpen {
|
||||
if invoice.State != ContractOpen {
|
||||
log.Debugf("cancelSingleHtlc: invoice %v no longer "+
|
||||
"open", invoiceRef)
|
||||
|
||||
@@ -664,8 +663,8 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
|
||||
// Lookup the current status of the htlc in the database.
|
||||
var (
|
||||
htlcState channeldb.HtlcState
|
||||
setID *channeldb.SetID
|
||||
htlcState HtlcState
|
||||
setID *SetID
|
||||
)
|
||||
htlc, ok := invoice.Htlcs[key]
|
||||
if !ok {
|
||||
@@ -695,7 +694,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
|
||||
// Cancellation is only possible if the htlc wasn't already
|
||||
// resolved.
|
||||
if htlcState != channeldb.HtlcStateAccepted {
|
||||
if htlcState != HtlcStateAccepted {
|
||||
log.Debugf("cancelSingleHtlc: htlc %v on invoice %v "+
|
||||
"is already resolved", key, invoiceRef)
|
||||
|
||||
@@ -707,11 +706,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
|
||||
// Return an update descriptor that cancels htlc and keeps
|
||||
// invoice open.
|
||||
canceledHtlcs := map[models.CircuitKey]struct{}{
|
||||
canceledHtlcs := map[CircuitKey]struct{}{
|
||||
key: {},
|
||||
}
|
||||
|
||||
return &channeldb.InvoiceUpdateDesc{
|
||||
return &InvoiceUpdateDesc{
|
||||
CancelHtlcs: canceledHtlcs,
|
||||
SetID: setID,
|
||||
}, nil
|
||||
@@ -720,11 +719,11 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
// Try to mark the specified htlc as canceled in the invoice database.
|
||||
// Intercept the update descriptor to set the local updated variable. If
|
||||
// no invoice update is performed, we can return early.
|
||||
setID := (*channeldb.SetID)(invoiceRef.SetID())
|
||||
setID := (*SetID)(invoiceRef.SetID())
|
||||
var updated bool
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, setID,
|
||||
func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
invoice, err := i.idb.UpdateInvoice(invoiceRef, setID,
|
||||
func(invoice *Invoice) (
|
||||
*InvoiceUpdateDesc, error) {
|
||||
|
||||
updateDesc, err := updateInvoice(invoice)
|
||||
if err != nil {
|
||||
@@ -748,7 +747,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef channeldb.InvoiceRef,
|
||||
if !ok {
|
||||
return fmt.Errorf("htlc %v not found", key)
|
||||
}
|
||||
if htlc.State == channeldb.HtlcStateCanceled {
|
||||
if htlc.State == HtlcStateCanceled {
|
||||
resolution := NewFailResolution(
|
||||
key, int32(htlc.AcceptHeight), result,
|
||||
)
|
||||
@@ -808,12 +807,12 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
|
||||
// to not be indexed. In the future, once AMP is merged, this should be
|
||||
// replaced by generating a random payment address on the behalf of the
|
||||
// sender.
|
||||
payAddr := channeldb.BlankPayAddr
|
||||
payAddr := BlankPayAddr
|
||||
|
||||
// Create placeholder invoice.
|
||||
invoice := &channeldb.Invoice{
|
||||
invoice := &Invoice{
|
||||
CreationDate: i.cfg.Clock.Now(),
|
||||
Terms: channeldb.ContractTerm{
|
||||
Terms: ContractTerm{
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
Value: amt,
|
||||
PaymentPreimage: &preimage,
|
||||
@@ -830,7 +829,7 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
|
||||
// Insert invoice into database. Ignore duplicates, because this
|
||||
// may be a replay.
|
||||
_, err = i.AddInvoice(invoice, ctx.hash)
|
||||
if err != nil && err != channeldb.ErrDuplicateInvoice {
|
||||
if err != nil && !errors.Is(err, ErrDuplicateInvoice) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -873,9 +872,9 @@ func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error {
|
||||
payAddr := ctx.mpp.PaymentAddr()
|
||||
|
||||
// Create placeholder invoice.
|
||||
invoice := &channeldb.Invoice{
|
||||
invoice := &Invoice{
|
||||
CreationDate: i.cfg.Clock.Now(),
|
||||
Terms: channeldb.ContractTerm{
|
||||
Terms: ContractTerm{
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
Value: amt,
|
||||
PaymentPreimage: nil,
|
||||
@@ -888,10 +887,10 @@ func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error {
|
||||
// payment addrs, this may be a replay or a different HTLC for the AMP
|
||||
// invoice.
|
||||
_, err := i.AddInvoice(invoice, ctx.hash)
|
||||
isDuplicatedInvoice := errors.Is(err, ErrDuplicateInvoice)
|
||||
isDuplicatedPayAddr := errors.Is(err, ErrDuplicatePayAddr)
|
||||
switch {
|
||||
case err == channeldb.ErrDuplicateInvoice:
|
||||
return nil
|
||||
case err == channeldb.ErrDuplicatePayAddr:
|
||||
case isDuplicatedInvoice || isDuplicatedPayAddr:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
@@ -915,7 +914,7 @@ func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error {
|
||||
// held htlc.
|
||||
func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
amtPaid lnwire.MilliSatoshi, expiry uint32, currentHeight int32,
|
||||
circuitKey models.CircuitKey, hodlChan chan<- interface{},
|
||||
circuitKey CircuitKey, hodlChan chan<- interface{},
|
||||
payload Payload) (HtlcResolution, error) {
|
||||
|
||||
// Create the update context containing the relevant details of the
|
||||
@@ -982,9 +981,9 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
||||
// main event loop.
|
||||
case *htlcAcceptResolution:
|
||||
if r.autoRelease {
|
||||
var invRef channeldb.InvoiceRef
|
||||
var invRef InvoiceRef
|
||||
if ctx.amp != nil {
|
||||
invRef = channeldb.InvoiceRefBySetID(*ctx.setID())
|
||||
invRef = InvoiceRefBySetID(*ctx.setID())
|
||||
} else {
|
||||
invRef = ctx.invoiceRef()
|
||||
}
|
||||
@@ -1025,29 +1024,29 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
resolution HtlcResolution
|
||||
updateSubscribers bool
|
||||
)
|
||||
invoice, err := i.cdb.UpdateInvoice(
|
||||
ctx.invoiceRef(),
|
||||
(*channeldb.SetID)(ctx.setID()),
|
||||
func(inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
updateDesc, res, err := updateInvoice(ctx, inv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
callback := func(inv *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
updateDesc, res, err := updateInvoice(ctx, inv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only send an update if the invoice state was changed.
|
||||
updateSubscribers = updateDesc != nil &&
|
||||
updateDesc.State != nil
|
||||
// Only send an update if the invoice state was changed.
|
||||
updateSubscribers = updateDesc != nil &&
|
||||
updateDesc.State != nil
|
||||
|
||||
// Assign resolution to outer scope variable.
|
||||
resolution = res
|
||||
// Assign resolution to outer scope variable.
|
||||
resolution = res
|
||||
|
||||
return updateDesc, nil
|
||||
},
|
||||
)
|
||||
return updateDesc, nil
|
||||
}
|
||||
|
||||
if _, ok := err.(channeldb.ErrDuplicateSetID); ok {
|
||||
invoiceRef := ctx.invoiceRef()
|
||||
setID := (*SetID)(ctx.setID())
|
||||
invoice, err := i.idb.UpdateInvoice(invoiceRef, setID, callback)
|
||||
|
||||
var duplicateSetIDErr ErrDuplicateSetID
|
||||
if errors.As(err, &duplicateSetIDErr) {
|
||||
return NewFailResolution(
|
||||
ctx.circuitKey, ctx.currentHeight,
|
||||
ResultInvoiceNotFound,
|
||||
@@ -1055,7 +1054,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
}
|
||||
|
||||
switch err {
|
||||
case channeldb.ErrInvoiceNotFound:
|
||||
case ErrInvoiceNotFound:
|
||||
// If the invoice was not found, return a failure resolution
|
||||
// with an invoice not found result.
|
||||
return NewFailResolution(
|
||||
@@ -1063,7 +1062,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
ResultInvoiceNotFound,
|
||||
), nil, nil
|
||||
|
||||
case channeldb.ErrInvRefEquivocation:
|
||||
case ErrInvRefEquivocation:
|
||||
return NewFailResolution(
|
||||
ctx.circuitKey, ctx.currentHeight,
|
||||
ResultInvoiceNotFound,
|
||||
@@ -1105,7 +1104,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// Also cancel any HTLCs in the HTLC set that are also in the
|
||||
// canceled state with the same failure result.
|
||||
setID := ctx.setID()
|
||||
canceledHtlcSet := invoice.HTLCSet(setID, channeldb.HtlcStateCanceled)
|
||||
canceledHtlcSet := invoice.HTLCSet(setID, HtlcStateCanceled)
|
||||
for key, htlc := range canceledHtlcSet {
|
||||
htlcFailResolution := NewFailResolution(
|
||||
key, int32(htlc.AcceptHeight), res.Outcome,
|
||||
@@ -1125,7 +1124,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// marked as settled, we should follow now and settle the htlc
|
||||
// with our peer.
|
||||
setID := ctx.setID()
|
||||
settledHtlcSet := invoice.HTLCSet(setID, channeldb.HtlcStateSettled)
|
||||
settledHtlcSet := invoice.HTLCSet(setID, HtlcStateSettled)
|
||||
for key, htlc := range settledHtlcSet {
|
||||
preimage := res.Preimage
|
||||
if htlc.AMP != nil && htlc.AMP.Preimage != nil {
|
||||
@@ -1155,7 +1154,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
//
|
||||
// TODO(roasbeef): can remove now??
|
||||
canceledHtlcSet := invoice.HTLCSetCompliment(
|
||||
setID, channeldb.HtlcStateCanceled,
|
||||
setID, HtlcStateCanceled,
|
||||
)
|
||||
for key, htlc := range canceledHtlcSet {
|
||||
htlcFailResolution := NewFailResolution(
|
||||
@@ -1189,7 +1188,7 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||
// Auto-release the htlc if the invoice is still open. It can
|
||||
// only happen for mpp payments that there are htlcs in state
|
||||
// Accepted while the invoice is Open.
|
||||
if invoice.State == channeldb.ContractOpen {
|
||||
if invoice.State == ContractOpen {
|
||||
res.acceptTime = invoiceHtlc.AcceptTime
|
||||
res.autoRelease = true
|
||||
}
|
||||
@@ -1231,29 +1230,31 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
updateInvoice := func(invoice *Invoice) (
|
||||
*InvoiceUpdateDesc, error) {
|
||||
|
||||
switch invoice.State {
|
||||
case channeldb.ContractOpen:
|
||||
return nil, channeldb.ErrInvoiceStillOpen
|
||||
case channeldb.ContractCanceled:
|
||||
return nil, channeldb.ErrInvoiceAlreadyCanceled
|
||||
case channeldb.ContractSettled:
|
||||
return nil, channeldb.ErrInvoiceAlreadySettled
|
||||
case ContractOpen:
|
||||
return nil, ErrInvoiceStillOpen
|
||||
|
||||
case ContractCanceled:
|
||||
return nil, ErrInvoiceAlreadyCanceled
|
||||
|
||||
case ContractSettled:
|
||||
return nil, ErrInvoiceAlreadySettled
|
||||
}
|
||||
|
||||
return &channeldb.InvoiceUpdateDesc{
|
||||
State: &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractSettled,
|
||||
return &InvoiceUpdateDesc{
|
||||
State: &InvoiceStateUpdateDesc{
|
||||
NewState: ContractSettled,
|
||||
Preimage: &preimage,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
hash := preimage.Hash()
|
||||
invoiceRef := channeldb.InvoiceRefByHash(hash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
invoiceRef := InvoiceRefByHash(hash)
|
||||
invoice, err := i.idb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
if err != nil {
|
||||
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
||||
preimage, err)
|
||||
@@ -1271,7 +1272,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
|
||||
// that were already settled before, will be notified again. This isn't
|
||||
// necessary but doesn't hurt either.
|
||||
for key, htlc := range invoice.Htlcs {
|
||||
if htlc.State != channeldb.HtlcStateSettled {
|
||||
if htlc.State != HtlcStateSettled {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1296,8 +1297,8 @@ func (i *InvoiceRegistry) CancelInvoice(payHash lntypes.Hash) error {
|
||||
// cancel already accepted invoices, taking our force cancel boolean into
|
||||
// account. This is pulled out into its own function so that tests that mock
|
||||
// cancelInvoiceImpl can reuse this logic.
|
||||
func shouldCancel(state channeldb.ContractState, cancelAccepted bool) bool {
|
||||
if state != channeldb.ContractAccepted {
|
||||
func shouldCancel(state ContractState, cancelAccepted bool) bool {
|
||||
if state != ContractAccepted {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1316,12 +1317,10 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
ref := channeldb.InvoiceRefByHash(payHash)
|
||||
ref := InvoiceRefByHash(payHash)
|
||||
log.Debugf("Invoice%v: canceling invoice", ref)
|
||||
|
||||
updateInvoice := func(invoice *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, error) {
|
||||
|
||||
updateInvoice := func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
if !shouldCancel(invoice.State, cancelAccepted) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1329,19 +1328,19 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
// Move invoice to the canceled state. Rely on validation in
|
||||
// channeldb to return an error if the invoice is already
|
||||
// settled or canceled.
|
||||
return &channeldb.InvoiceUpdateDesc{
|
||||
State: &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractCanceled,
|
||||
return &InvoiceUpdateDesc{
|
||||
State: &InvoiceStateUpdateDesc{
|
||||
NewState: ContractCanceled,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
invoiceRef := channeldb.InvoiceRefByHash(payHash)
|
||||
invoice, err := i.cdb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
invoiceRef := InvoiceRefByHash(payHash)
|
||||
invoice, err := i.idb.UpdateInvoice(invoiceRef, nil, updateInvoice)
|
||||
|
||||
// Implement idempotency by returning success if the invoice was already
|
||||
// canceled.
|
||||
if err == channeldb.ErrInvoiceAlreadyCanceled {
|
||||
if errors.Is(err, ErrInvoiceAlreadyCanceled) {
|
||||
log.Debugf("Invoice%v: already canceled", ref)
|
||||
return nil
|
||||
}
|
||||
@@ -1350,7 +1349,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
}
|
||||
|
||||
// Return without cancellation if the invoice state is ContractAccepted.
|
||||
if invoice.State == channeldb.ContractAccepted {
|
||||
if invoice.State == ContractAccepted {
|
||||
log.Debugf("Invoice%v: remains accepted as cancel wasn't"+
|
||||
"explicitly requested.", ref)
|
||||
return nil
|
||||
@@ -1364,7 +1363,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
// before, will be notified again. This isn't necessary but doesn't hurt
|
||||
// either.
|
||||
for key, htlc := range invoice.Htlcs {
|
||||
if htlc.State != channeldb.HtlcStateCanceled {
|
||||
if htlc.State != HtlcStateCanceled {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1381,24 +1380,22 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
if i.cfg.GcCanceledInvoicesOnTheFly {
|
||||
// Assemble the delete reference and attempt to delete through
|
||||
// the invocice from the DB.
|
||||
deleteRef := channeldb.InvoiceDeleteRef{
|
||||
deleteRef := InvoiceDeleteRef{
|
||||
PayHash: payHash,
|
||||
AddIndex: invoice.AddIndex,
|
||||
SettleIndex: invoice.SettleIndex,
|
||||
}
|
||||
if invoice.Terms.PaymentAddr != channeldb.BlankPayAddr {
|
||||
if invoice.Terms.PaymentAddr != BlankPayAddr {
|
||||
deleteRef.PayAddr = &invoice.Terms.PaymentAddr
|
||||
}
|
||||
|
||||
err = i.cdb.DeleteInvoice(
|
||||
[]channeldb.InvoiceDeleteRef{deleteRef},
|
||||
)
|
||||
err = i.idb.DeleteInvoice([]InvoiceDeleteRef{deleteRef})
|
||||
// If by any chance deletion failed, then log it instead of
|
||||
// returning the error, as the invoice itsels has already been
|
||||
// returning the error, as the invoice itself has already been
|
||||
// canceled.
|
||||
if err != nil {
|
||||
log.Warnf("Invoice%v could not be deleted: %v",
|
||||
ref, err)
|
||||
log.Warnf("Invoice %v could not be deleted: %v", ref,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1408,7 +1405,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
|
||||
// notifyClients notifies all currently registered invoice notification clients
|
||||
// of a newly added/settled invoice.
|
||||
func (i *InvoiceRegistry) notifyClients(hash lntypes.Hash,
|
||||
invoice *channeldb.Invoice, setID *[32]byte) {
|
||||
invoice *Invoice, setID *[32]byte) {
|
||||
|
||||
event := &invoiceEvent{
|
||||
invoice: invoice,
|
||||
@@ -1451,12 +1448,12 @@ type InvoiceSubscription struct {
|
||||
// NewInvoices is a channel that we'll use to send all newly created
|
||||
// invoices with an invoice index greater than the specified
|
||||
// StartingInvoiceIndex field.
|
||||
NewInvoices chan *channeldb.Invoice
|
||||
NewInvoices chan *Invoice
|
||||
|
||||
// SettledInvoices is a channel that we'll use to send all settled
|
||||
// invoices with an invoices index greater than the specified
|
||||
// StartingInvoiceIndex field.
|
||||
SettledInvoices chan *channeldb.Invoice
|
||||
SettledInvoices chan *Invoice
|
||||
|
||||
// addIndex is the highest add index the caller knows of. We'll use
|
||||
// this information to send out an event backlog to the notifications
|
||||
@@ -1477,11 +1474,19 @@ type InvoiceSubscription struct {
|
||||
type SingleInvoiceSubscription struct {
|
||||
invoiceSubscriptionKit
|
||||
|
||||
invoiceRef channeldb.InvoiceRef
|
||||
invoiceRef InvoiceRef
|
||||
|
||||
// Updates is a channel that we'll use to send all invoice events for
|
||||
// the invoice that is subscribed to.
|
||||
Updates chan *channeldb.Invoice
|
||||
Updates chan *Invoice
|
||||
}
|
||||
|
||||
// PayHash returns the optional payment hash of the target invoice.
|
||||
//
|
||||
// TODO(positiveblue): This method is only supposed to be used in tests. It will
|
||||
// be deleted as soon as invoiceregistery_test is in the same module.
|
||||
func (s *SingleInvoiceSubscription) PayHash() *lntypes.Hash {
|
||||
return s.invoiceRef.PayHash()
|
||||
}
|
||||
|
||||
// Cancel unregisters the InvoiceSubscription, freeing any previously allocated
|
||||
@@ -1498,6 +1503,7 @@ func (i *invoiceSubscriptionKit) Cancel() {
|
||||
func (i *invoiceSubscriptionKit) notify(event *invoiceEvent) error {
|
||||
select {
|
||||
case i.ntfnQueue.ChanIn() <- event:
|
||||
|
||||
case <-i.cancelChan:
|
||||
// This can only be triggered by delivery of non-backlog
|
||||
// events.
|
||||
@@ -1518,8 +1524,8 @@ func (i *InvoiceRegistry) SubscribeNotifications(
|
||||
addIndex, settleIndex uint64) (*InvoiceSubscription, error) {
|
||||
|
||||
client := &InvoiceSubscription{
|
||||
NewInvoices: make(chan *channeldb.Invoice),
|
||||
SettledInvoices: make(chan *channeldb.Invoice),
|
||||
NewInvoices: make(chan *Invoice),
|
||||
SettledInvoices: make(chan *Invoice),
|
||||
addIndex: addIndex,
|
||||
settleIndex: settleIndex,
|
||||
invoiceSubscriptionKit: invoiceSubscriptionKit{
|
||||
@@ -1556,24 +1562,25 @@ func (i *InvoiceRegistry) SubscribeNotifications(
|
||||
case ntfn := <-client.ntfnQueue.ChanOut():
|
||||
invoiceEvent := ntfn.(*invoiceEvent)
|
||||
|
||||
var targetChan chan *channeldb.Invoice
|
||||
var targetChan chan *Invoice
|
||||
state := invoiceEvent.invoice.State
|
||||
switch {
|
||||
// AMP invoices never move to settled, but will
|
||||
// be sent with a set ID if an HTLC set is
|
||||
// being settled.
|
||||
case state == channeldb.ContractOpen &&
|
||||
case state == ContractOpen &&
|
||||
invoiceEvent.setID != nil:
|
||||
fallthrough
|
||||
case state == channeldb.ContractSettled:
|
||||
|
||||
case state == ContractSettled:
|
||||
targetChan = client.SettledInvoices
|
||||
|
||||
case state == channeldb.ContractOpen:
|
||||
case state == ContractOpen:
|
||||
targetChan = client.NewInvoices
|
||||
|
||||
default:
|
||||
log.Errorf("unknown invoice "+
|
||||
"state: %v", state)
|
||||
log.Errorf("unknown invoice state: %v",
|
||||
state)
|
||||
|
||||
continue
|
||||
}
|
||||
@@ -1619,14 +1626,14 @@ func (i *InvoiceRegistry) SubscribeSingleInvoice(
|
||||
hash lntypes.Hash) (*SingleInvoiceSubscription, error) {
|
||||
|
||||
client := &SingleInvoiceSubscription{
|
||||
Updates: make(chan *channeldb.Invoice),
|
||||
Updates: make(chan *Invoice),
|
||||
invoiceSubscriptionKit: invoiceSubscriptionKit{
|
||||
quit: i.quit,
|
||||
ntfnQueue: queue.NewConcurrentQueue(20),
|
||||
cancelChan: make(chan struct{}),
|
||||
backlogDelivered: make(chan struct{}),
|
||||
},
|
||||
invoiceRef: channeldb.InvoiceRefByHash(hash),
|
||||
invoiceRef: InvoiceRefByHash(hash),
|
||||
}
|
||||
client.ntfnQueue.Start()
|
||||
|
||||
@@ -1720,7 +1727,7 @@ func (i *InvoiceRegistry) notifyHodlSubscribers(htlcResolution HtlcResolution) {
|
||||
|
||||
// hodlSubscribe adds a new invoice subscription.
|
||||
func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{},
|
||||
circuitKey models.CircuitKey) {
|
||||
circuitKey CircuitKey) {
|
||||
|
||||
i.hodlSubscriptionsMux.Lock()
|
||||
defer i.hodlSubscriptionsMux.Unlock()
|
||||
@@ -1736,7 +1743,7 @@ func (i *InvoiceRegistry) hodlSubscribe(subscriber chan<- interface{},
|
||||
|
||||
reverseSubscriptions, ok := i.hodlReverseSubscriptions[subscriber]
|
||||
if !ok {
|
||||
reverseSubscriptions = make(map[models.CircuitKey]struct{})
|
||||
reverseSubscriptions = make(map[CircuitKey]struct{})
|
||||
i.hodlReverseSubscriptions[subscriber] = reverseSubscriptions
|
||||
}
|
||||
reverseSubscriptions[circuitKey] = struct{}{}
|
||||
@@ -1757,7 +1764,7 @@ func (i *InvoiceRegistry) HodlUnsubscribeAll(subscriber chan<- interface{}) {
|
||||
|
||||
// copySingleClients copies i.SingleInvoiceSubscription inside a lock. This is
|
||||
// useful when we need to iterate the map to send notifications.
|
||||
func (i *InvoiceRegistry) copySingleClients() map[uint32]*SingleInvoiceSubscription {
|
||||
func (i *InvoiceRegistry) copySingleClients() map[uint32]*SingleInvoiceSubscription { //nolint:lll
|
||||
i.notificationClientMux.RLock()
|
||||
defer i.notificationClientMux.RUnlock()
|
||||
|
||||
|
||||
+201
-191
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,795 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxMemoSize is maximum size of the memo field within invoices stored
|
||||
// in the database.
|
||||
MaxMemoSize = 1024
|
||||
|
||||
// MaxPaymentRequestSize is the max size of a payment request for
|
||||
// this invoice.
|
||||
// TODO(halseth): determine the max length payment request when field
|
||||
// lengths are final.
|
||||
MaxPaymentRequestSize = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
// unknownPreimage is an all-zeroes preimage that indicates that the
|
||||
// preimage for this invoice is not yet known.
|
||||
UnknownPreimage lntypes.Preimage
|
||||
|
||||
// BlankPayAddr is a sentinel payment address for legacy invoices.
|
||||
// Invoices with this payment address are special-cased in the insertion
|
||||
// logic to prevent being indexed in the payment address index,
|
||||
// otherwise they would cause collisions after the first insertion.
|
||||
BlankPayAddr [32]byte
|
||||
)
|
||||
|
||||
// RefModifier is a modification on top of a base invoice ref. It allows the
|
||||
// caller to opt to skip out on HTLCs for a given payAddr, or only return the
|
||||
// set of specified HTLCs for a given setID.
|
||||
type RefModifier uint8
|
||||
|
||||
const (
|
||||
// DefaultModifier is the base modifier that doesn't change any
|
||||
// behavior.
|
||||
DefaultModifier RefModifier = iota
|
||||
|
||||
// HtlcSetOnlyModifier can only be used with a setID based invoice ref,
|
||||
// and specifies that only the set of HTLCs related to that setID are
|
||||
// to be returned.
|
||||
HtlcSetOnlyModifier
|
||||
|
||||
// HtlcSetOnlyModifier can only be used with a payAddr based invoice
|
||||
// ref, and specifies that the returned invoice shouldn't include any
|
||||
// HTLCs at all.
|
||||
HtlcSetBlankModifier
|
||||
)
|
||||
|
||||
// InvoiceRef is a composite identifier for invoices. Invoices can be referenced
|
||||
// by various combinations of payment hash and payment addr, in certain contexts
|
||||
// only some of these are known. An InvoiceRef and its constructors thus
|
||||
// encapsulate the valid combinations of query parameters that can be supplied
|
||||
// to LookupInvoice and UpdateInvoice.
|
||||
type InvoiceRef struct {
|
||||
// payHash is the payment hash of the target invoice. All invoices are
|
||||
// currently indexed by payment hash. This value will be used as a
|
||||
// fallback when no payment address is known.
|
||||
payHash *lntypes.Hash
|
||||
|
||||
// payAddr is the payment addr of the target invoice. Newer invoices
|
||||
// (0.11 and up) are indexed by payment address in addition to payment
|
||||
// hash, but pre 0.8 invoices do not have one at all. When this value is
|
||||
// known it will be used as the primary identifier, falling back to
|
||||
// payHash if no value is known.
|
||||
payAddr *[32]byte
|
||||
|
||||
// setID is the optional set id for an AMP payment. This can be used to
|
||||
// lookup or update the invoice knowing only this value. Queries by set
|
||||
// id are only used to facilitate user-facing requests, e.g. lookup,
|
||||
// settle or cancel an AMP invoice. The regular update flow from the
|
||||
// invoice registry will always query for the invoice by
|
||||
// payHash+payAddr.
|
||||
setID *[32]byte
|
||||
|
||||
// refModifier allows an invoice ref to include or exclude specific
|
||||
// HTLC sets based on the payAddr or setId.
|
||||
refModifier RefModifier
|
||||
}
|
||||
|
||||
// InvoiceRefByHash creates an InvoiceRef that queries for an invoice only by
|
||||
// its payment hash.
|
||||
func InvoiceRefByHash(payHash lntypes.Hash) InvoiceRef {
|
||||
return InvoiceRef{
|
||||
payHash: &payHash,
|
||||
}
|
||||
}
|
||||
|
||||
// InvoiceRefByHashAndAddr creates an InvoiceRef that first queries for an
|
||||
// invoice by the provided payment address, falling back to the payment hash if
|
||||
// the payment address is unknown.
|
||||
func InvoiceRefByHashAndAddr(payHash lntypes.Hash,
|
||||
payAddr [32]byte) InvoiceRef {
|
||||
|
||||
return InvoiceRef{
|
||||
payHash: &payHash,
|
||||
payAddr: &payAddr,
|
||||
}
|
||||
}
|
||||
|
||||
// InvoiceRefByAddr creates an InvoiceRef that queries the payment addr index
|
||||
// for an invoice with the provided payment address.
|
||||
func InvoiceRefByAddr(addr [32]byte) InvoiceRef {
|
||||
return InvoiceRef{
|
||||
payAddr: &addr,
|
||||
}
|
||||
}
|
||||
|
||||
// InvoiceRefByAddrBlankHtlc creates an InvoiceRef that queries the payment addr
|
||||
// index for an invoice with the provided payment address, but excludes any of
|
||||
// the core HTLC information.
|
||||
func InvoiceRefByAddrBlankHtlc(addr [32]byte) InvoiceRef {
|
||||
return InvoiceRef{
|
||||
payAddr: &addr,
|
||||
refModifier: HtlcSetBlankModifier,
|
||||
}
|
||||
}
|
||||
|
||||
// InvoiceRefBySetID creates an InvoiceRef that queries the set id index for an
|
||||
// invoice with the provided setID. If the invoice is not found, the query will
|
||||
// not fallback to payHash or payAddr.
|
||||
func InvoiceRefBySetID(setID [32]byte) InvoiceRef {
|
||||
return InvoiceRef{
|
||||
setID: &setID,
|
||||
}
|
||||
}
|
||||
|
||||
// InvoiceRefBySetIDFiltered is similar to the InvoiceRefBySetID identifier,
|
||||
// but it specifies that the returned set of HTLCs should be filtered to only
|
||||
// include HTLCs that are part of that set.
|
||||
func InvoiceRefBySetIDFiltered(setID [32]byte) InvoiceRef {
|
||||
return InvoiceRef{
|
||||
setID: &setID,
|
||||
refModifier: HtlcSetOnlyModifier,
|
||||
}
|
||||
}
|
||||
|
||||
// PayHash returns the optional payment hash of the target invoice.
|
||||
//
|
||||
// NOTE: This value may be nil.
|
||||
func (r InvoiceRef) PayHash() *lntypes.Hash {
|
||||
if r.payHash != nil {
|
||||
hash := *r.payHash
|
||||
return &hash
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PayAddr returns the optional payment address of the target invoice.
|
||||
//
|
||||
// NOTE: This value may be nil.
|
||||
func (r InvoiceRef) PayAddr() *[32]byte {
|
||||
if r.payAddr != nil {
|
||||
addr := *r.payAddr
|
||||
return &addr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetID returns the optional set id of the target invoice.
|
||||
//
|
||||
// NOTE: This value may be nil.
|
||||
func (r InvoiceRef) SetID() *[32]byte {
|
||||
if r.setID != nil {
|
||||
id := *r.setID
|
||||
return &id
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Modifier defines the set of available modifications to the base invoice ref
|
||||
// look up that are available.
|
||||
func (r InvoiceRef) Modifier() RefModifier {
|
||||
return r.refModifier
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of an InvoiceRef.
|
||||
func (r InvoiceRef) String() string {
|
||||
var ids []string
|
||||
if r.payHash != nil {
|
||||
ids = append(ids, fmt.Sprintf("pay_hash=%v", *r.payHash))
|
||||
}
|
||||
if r.payAddr != nil {
|
||||
ids = append(ids, fmt.Sprintf("pay_addr=%x", *r.payAddr))
|
||||
}
|
||||
if r.setID != nil {
|
||||
ids = append(ids, fmt.Sprintf("set_id=%x", *r.setID))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("(%s)", strings.Join(ids, ", "))
|
||||
}
|
||||
|
||||
// ContractState describes the state the invoice is in.
|
||||
type ContractState uint8
|
||||
|
||||
const (
|
||||
// ContractOpen means the invoice has only been created.
|
||||
ContractOpen ContractState = 0
|
||||
|
||||
// ContractSettled means the htlc is settled and the invoice has been
|
||||
// paid.
|
||||
ContractSettled ContractState = 1
|
||||
|
||||
// ContractCanceled means the invoice has been canceled.
|
||||
ContractCanceled ContractState = 2
|
||||
|
||||
// ContractAccepted means the HTLC has been accepted but not settled
|
||||
// yet.
|
||||
ContractAccepted ContractState = 3
|
||||
)
|
||||
|
||||
// String returns a human readable identifier for the ContractState type.
|
||||
func (c ContractState) String() string {
|
||||
switch c {
|
||||
case ContractOpen:
|
||||
return "Open"
|
||||
|
||||
case ContractSettled:
|
||||
return "Settled"
|
||||
|
||||
case ContractCanceled:
|
||||
return "Canceled"
|
||||
|
||||
case ContractAccepted:
|
||||
return "Accepted"
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// IsFinal returns a boolean indicating whether an invoice state is final.
|
||||
func (c ContractState) IsFinal() bool {
|
||||
return c == ContractSettled || c == ContractCanceled
|
||||
}
|
||||
|
||||
// ContractTerm is a companion struct to the Invoice struct. This struct houses
|
||||
// the necessary conditions required before the invoice can be considered fully
|
||||
// settled by the payee.
|
||||
type ContractTerm struct {
|
||||
// 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
|
||||
|
||||
// PaymentPreimage is the preimage which is to be revealed in the
|
||||
// occasion that an HTLC paying to the hash of this preimage is
|
||||
// extended. Set to nil if the preimage isn't known yet.
|
||||
PaymentPreimage *lntypes.Preimage
|
||||
|
||||
// Value is the expected amount of milli-satoshis to be paid to an HTLC
|
||||
// which can be satisfied by the above preimage.
|
||||
Value lnwire.MilliSatoshi
|
||||
|
||||
// PaymentAddr is a randomly generated value include in the MPP record
|
||||
// by the sender to prevent probing of the receiver.
|
||||
PaymentAddr [32]byte
|
||||
|
||||
// Features is the feature vectors advertised on the payment request.
|
||||
Features *lnwire.FeatureVector
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the prominent contract terms.
|
||||
func (c ContractTerm) String() string {
|
||||
return fmt.Sprintf("amt=%v, expiry=%v, final_cltv_delta=%v", c.Value,
|
||||
c.Expiry, c.FinalCltvDelta)
|
||||
}
|
||||
|
||||
// SetID is the extra unique tuple item for AMP invoices. In addition to
|
||||
// setting a payment address, each repeated payment to an AMP invoice will also
|
||||
// contain a set ID as well.
|
||||
type SetID [32]byte
|
||||
|
||||
// InvoiceStateAMP is a struct that associates the current state of an AMP
|
||||
// invoice identified by its set ID along with the set of invoices identified
|
||||
// by the circuit key. This allows callers to easily look up the latest state
|
||||
// of an AMP "sub-invoice" and also look up the invoice HLTCs themselves in the
|
||||
// greater HTLC map index.
|
||||
type InvoiceStateAMP struct {
|
||||
// State is the state of this sub-AMP invoice.
|
||||
State HtlcState
|
||||
|
||||
// SettleIndex indicates the location in the settle index that
|
||||
// references this instance of InvoiceStateAMP, but only if
|
||||
// this value is set (non-zero), and State is HtlcStateSettled.
|
||||
SettleIndex uint64
|
||||
|
||||
// SettleDate is the date that the setID was settled.
|
||||
SettleDate time.Time
|
||||
|
||||
// InvoiceKeys is the set of circuit keys that can be used to locate
|
||||
// the invoices for a given set ID.
|
||||
InvoiceKeys map[CircuitKey]struct{}
|
||||
|
||||
// AmtPaid is the total amount that was paid in the AMP sub-invoice.
|
||||
// Fetching the full HTLC/invoice state allows one to extract the
|
||||
// custom records as well as the break down of the payment splits used
|
||||
// when paying.
|
||||
AmtPaid lnwire.MilliSatoshi
|
||||
}
|
||||
|
||||
// copy makes a deep copy of the underlying InvoiceStateAMP.
|
||||
func (i *InvoiceStateAMP) copy() (InvoiceStateAMP, error) {
|
||||
result := *i
|
||||
|
||||
// Make a copy of the InvoiceKeys map.
|
||||
result.InvoiceKeys = make(map[CircuitKey]struct{})
|
||||
for k := range i.InvoiceKeys {
|
||||
result.InvoiceKeys[k] = struct{}{}
|
||||
}
|
||||
|
||||
// As a safety measure, copy SettleDate. time.Time is concurrency safe
|
||||
// except when using any of the (un)marshalling methods.
|
||||
settleDateBytes, err := i.SettleDate.MarshalBinary()
|
||||
if err != nil {
|
||||
return InvoiceStateAMP{}, err
|
||||
}
|
||||
|
||||
err = result.SettleDate.UnmarshalBinary(settleDateBytes)
|
||||
if err != nil {
|
||||
return InvoiceStateAMP{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AMPInvoiceState represents a type that stores metadata related to the set of
|
||||
// settled AMP "sub-invoices".
|
||||
type AMPInvoiceState map[SetID]InvoiceStateAMP
|
||||
|
||||
// Invoice is a payment invoice generated by a payee in order to request
|
||||
// payment for some good or service. The inclusion of invoices within Lightning
|
||||
// creates a payment work flow for merchants very similar to that of the
|
||||
// existing financial system within PayPal, etc. Invoices are added to the
|
||||
// database when a payment is requested, then can be settled manually once the
|
||||
// payment is received at the upper layer. For record keeping purposes,
|
||||
// invoices are never deleted from the database, instead a bit is toggled
|
||||
// denoting the invoice has been fully settled. Within the database, all
|
||||
// invoices must have a unique payment hash which is generated by taking the
|
||||
// sha256 of the payment preimage.
|
||||
type Invoice struct {
|
||||
// Memo is an optional memo to be stored along side an invoice. The
|
||||
// memo may contain further details pertaining to the invoice itself,
|
||||
// or any other message which fits within the size constraints.
|
||||
Memo []byte
|
||||
|
||||
// PaymentRequest is the encoded payment request for this invoice. For
|
||||
// spontaneous (keysend) payments, this field will be empty.
|
||||
PaymentRequest []byte
|
||||
|
||||
// CreationDate is the exact time the invoice was created.
|
||||
CreationDate time.Time
|
||||
|
||||
// SettleDate is the exact time the invoice was settled.
|
||||
SettleDate time.Time
|
||||
|
||||
// Terms are the contractual payment terms of the invoice. Once all the
|
||||
// terms have been satisfied by the payer, then the invoice can be
|
||||
// considered fully fulfilled.
|
||||
//
|
||||
// TODO(roasbeef): later allow for multiple terms to fulfill the final
|
||||
// invoice: payment fragmentation, etc.
|
||||
Terms ContractTerm
|
||||
|
||||
// AddIndex is an auto-incrementing integer that acts as a
|
||||
// monotonically increasing sequence number for all invoices created.
|
||||
// Clients can then use this field as a "checkpoint" of sorts when
|
||||
// implementing a streaming RPC to notify consumers of instances where
|
||||
// an invoice has been added before they re-connected.
|
||||
//
|
||||
// NOTE: This index starts at 1.
|
||||
AddIndex uint64
|
||||
|
||||
// SettleIndex is an auto-incrementing integer that acts as a
|
||||
// monotonically increasing sequence number for all settled invoices.
|
||||
// Clients can then use this field as a "checkpoint" of sorts when
|
||||
// implementing a streaming RPC to notify consumers of instances where
|
||||
// an invoice has been settled before they re-connected.
|
||||
//
|
||||
// NOTE: This index starts at 1.
|
||||
SettleIndex uint64
|
||||
|
||||
// State describes the state the invoice is in. This is the global
|
||||
// state of the invoice which may remain open even when a series of
|
||||
// sub-invoices for this invoice has been settled.
|
||||
State ContractState
|
||||
|
||||
// AmtPaid is the final amount that we ultimately accepted for pay for
|
||||
// this invoice. We specify this value independently as it's possible
|
||||
// that the invoice originally didn't specify an amount, or the sender
|
||||
// overpaid.
|
||||
AmtPaid lnwire.MilliSatoshi
|
||||
|
||||
// Htlcs records all htlcs that paid to this invoice. Some of these
|
||||
// htlcs may have been marked as canceled.
|
||||
Htlcs map[CircuitKey]*InvoiceHTLC
|
||||
|
||||
// AMPState describes the state of any related sub-invoices AMP to this
|
||||
// greater invoice. A sub-invoice is defined by a set of HTLCs with the
|
||||
// same set ID that attempt to make one time or recurring payments to
|
||||
// this greater invoice. It's possible for a sub-invoice to be canceled
|
||||
// or settled, but the greater invoice still open.
|
||||
AMPState AMPInvoiceState
|
||||
|
||||
// HodlInvoice indicates whether the invoice should be held in the
|
||||
// Accepted state or be settled right away.
|
||||
HodlInvoice bool
|
||||
}
|
||||
|
||||
// HTLCSet returns the set of HTLCs belonging to setID and in the provided
|
||||
// state. Passing a nil setID will return all HTLCs in the provided state in the
|
||||
// case of legacy or MPP, and no HTLCs in the case of AMP. Otherwise, the
|
||||
// returned set will be filtered by the populated setID which is used to
|
||||
// retrieve AMP HTLC sets.
|
||||
func (i *Invoice) HTLCSet(setID *[32]byte,
|
||||
state HtlcState) map[CircuitKey]*InvoiceHTLC {
|
||||
|
||||
htlcSet := make(map[CircuitKey]*InvoiceHTLC)
|
||||
for key, htlc := range i.Htlcs {
|
||||
// Only add HTLCs that are in the requested HtlcState.
|
||||
if htlc.State != state {
|
||||
continue
|
||||
}
|
||||
|
||||
if !htlc.IsInHTLCSet(setID) {
|
||||
continue
|
||||
}
|
||||
|
||||
htlcSet[key] = htlc
|
||||
}
|
||||
|
||||
return htlcSet
|
||||
}
|
||||
|
||||
// HTLCSetCompliment returns the set of all HTLCs not belonging to setID that
|
||||
// are in the target state. Passing a nil setID will return no invoices, since
|
||||
// all MPP HTLCs are part of the same HTLC set.
|
||||
func (i *Invoice) HTLCSetCompliment(setID *[32]byte,
|
||||
state HtlcState) map[CircuitKey]*InvoiceHTLC {
|
||||
|
||||
htlcSet := make(map[CircuitKey]*InvoiceHTLC)
|
||||
for key, htlc := range i.Htlcs {
|
||||
// Only add HTLCs that are in the requested HtlcState.
|
||||
if htlc.State != state {
|
||||
continue
|
||||
}
|
||||
|
||||
// We are constructing the compliment, so filter anything that
|
||||
// matches this set id.
|
||||
if htlc.IsInHTLCSet(setID) {
|
||||
continue
|
||||
}
|
||||
|
||||
htlcSet[key] = htlc
|
||||
}
|
||||
|
||||
return htlcSet
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// HtlcStateCanceled indicates the htlc is canceled back to the
|
||||
// sender.
|
||||
HtlcStateCanceled
|
||||
|
||||
// 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
|
||||
|
||||
// MppTotalAmt is a field for mpp that indicates the expected total
|
||||
// amount.
|
||||
MppTotalAmt 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
|
||||
// canceled htlc isn't just removed from the invoice htlcs map, because
|
||||
// we need AcceptHeight to properly cancel the htlc back.
|
||||
State HtlcState
|
||||
|
||||
// CustomRecords contains the custom key/value pairs that accompanied
|
||||
// the htlc.
|
||||
CustomRecords record.CustomSet
|
||||
|
||||
// AMP encapsulates additional data relevant to AMP HTLCs. This includes
|
||||
// the AMP onion record, in addition to the HTLC's payment hash and
|
||||
// preimage since these are unique to each AMP HTLC, and not the invoice
|
||||
// as a whole.
|
||||
//
|
||||
// NOTE: This value will only be set for AMP HTLCs.
|
||||
AMP *InvoiceHtlcAMPData
|
||||
}
|
||||
|
||||
// Copy makes a deep copy of the target InvoiceHTLC.
|
||||
func (h *InvoiceHTLC) Copy() *InvoiceHTLC {
|
||||
result := *h
|
||||
|
||||
// Make a copy of the CustomSet map.
|
||||
result.CustomRecords = make(record.CustomSet)
|
||||
for k, v := range h.CustomRecords {
|
||||
result.CustomRecords[k] = v
|
||||
}
|
||||
|
||||
result.AMP = h.AMP.Copy()
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// IsInHTLCSet returns true if this HTLC is part an HTLC set. If nil is passed,
|
||||
// this method returns true if this is an MPP HTLC. Otherwise, it only returns
|
||||
// true if the AMP HTLC's set id matches the populated setID.
|
||||
func (h *InvoiceHTLC) IsInHTLCSet(setID *[32]byte) bool {
|
||||
wantAMPSet := setID != nil
|
||||
isAMPHtlc := h.AMP != nil
|
||||
|
||||
// Non-AMP HTLCs cannot be part of AMP HTLC sets, and vice versa.
|
||||
if wantAMPSet != isAMPHtlc {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip AMP HTLCs that have differing set ids.
|
||||
if isAMPHtlc && *setID != h.AMP.Record.SetID() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// InvoiceHtlcAMPData is a struct hodling the additional metadata stored for
|
||||
// each received AMP HTLC. This includes the AMP onion record, in addition to
|
||||
// the HTLC's payment hash and preimage.
|
||||
type InvoiceHtlcAMPData struct {
|
||||
// AMP is a copy of the AMP record presented in the onion payload
|
||||
// containing the information necessary to correlate and settle a
|
||||
// spontaneous HTLC set. Newly accepted legacy keysend payments will
|
||||
// also have this field set as we automatically promote them into an AMP
|
||||
// payment for internal processing.
|
||||
Record record.AMP
|
||||
|
||||
// Hash is an HTLC-level payment hash that is stored only for AMP
|
||||
// payments. This is done because an AMP HTLC will carry a different
|
||||
// payment hash from the invoice it might be satisfying, so we track the
|
||||
// payment hashes individually to able to compute whether or not the
|
||||
// reconstructed preimage correctly matches the HTLC's hash.
|
||||
Hash lntypes.Hash
|
||||
|
||||
// Preimage is an HTLC-level preimage that satisfies the AMP HTLC's
|
||||
// Hash. The preimage will be derived either from secret share
|
||||
// reconstruction of the shares in the AMP payload.
|
||||
//
|
||||
// NOTE: Preimage will only be present once the HTLC is in
|
||||
// HtlcStateSettled.
|
||||
Preimage *lntypes.Preimage
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the InvoiceHtlcAMPData.
|
||||
func (d *InvoiceHtlcAMPData) Copy() *InvoiceHtlcAMPData {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var preimage *lntypes.Preimage
|
||||
if d.Preimage != nil {
|
||||
pimg := *d.Preimage
|
||||
preimage = &pimg
|
||||
}
|
||||
|
||||
return &InvoiceHtlcAMPData{
|
||||
Record: d.Record,
|
||||
Hash: d.Hash,
|
||||
Preimage: preimage,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// MppTotalAmt is a field for mpp that indicates the expected total
|
||||
// amount.
|
||||
MppTotalAmt lnwire.MilliSatoshi
|
||||
|
||||
// Expiry is the expiry height of this htlc.
|
||||
Expiry uint32
|
||||
|
||||
// CustomRecords contains the custom key/value pairs that accompanied
|
||||
// the htlc.
|
||||
CustomRecords record.CustomSet
|
||||
|
||||
// AMP encapsulates additional data relevant to AMP HTLCs. This includes
|
||||
// the AMP onion record, in addition to the HTLC's payment hash and
|
||||
// preimage since these are unique to each AMP HTLC, and not the invoice
|
||||
// as a whole.
|
||||
//
|
||||
// NOTE: This value will only be set for AMP HTLCs.
|
||||
AMP *InvoiceHtlcAMPData
|
||||
}
|
||||
|
||||
// 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. If nil,
|
||||
// the state is left unchanged.
|
||||
State *InvoiceStateUpdateDesc
|
||||
|
||||
// CancelHtlcs describes the htlcs that need to be canceled.
|
||||
CancelHtlcs map[CircuitKey]struct{}
|
||||
|
||||
// AddHtlcs describes the newly accepted htlcs that need to be added to
|
||||
// the invoice.
|
||||
AddHtlcs map[CircuitKey]*HtlcAcceptDesc
|
||||
|
||||
// SetID is an optional set ID for AMP invoices that allows operations
|
||||
// to be more efficient by ensuring we don't need to read out the
|
||||
// entire HTLC set each timee an HTLC is to be cancelled.
|
||||
SetID *SetID
|
||||
}
|
||||
|
||||
// InvoiceStateUpdateDesc describes an invoice-level state transition.
|
||||
type InvoiceStateUpdateDesc struct {
|
||||
// NewState is the new state that this invoice should progress to.
|
||||
NewState ContractState
|
||||
|
||||
// Preimage must be set to the preimage when NewState is settled.
|
||||
Preimage *lntypes.Preimage
|
||||
|
||||
// HTLCPreimages set the HTLC-level preimages stored for AMP HTLCs.
|
||||
// These are only learned when settling the invoice as a whole. Must be
|
||||
// set when settling an invoice with non-nil SetID.
|
||||
HTLCPreimages map[CircuitKey]lntypes.Preimage
|
||||
|
||||
// SetID identifies a specific set of HTLCs destined for the same
|
||||
// invoice as part of a larger AMP payment. This value will be nil for
|
||||
// legacy or MPP payments.
|
||||
SetID *[32]byte
|
||||
}
|
||||
|
||||
// InvoiceUpdateCallback is a callback used in the db transaction to update the
|
||||
// invoice.
|
||||
type InvoiceUpdateCallback = func(invoice *Invoice) (*InvoiceUpdateDesc, error)
|
||||
|
||||
func ValidateInvoice(i *Invoice, paymentHash lntypes.Hash) error {
|
||||
// Avoid conflicts with all-zeroes magic value in the database.
|
||||
if paymentHash == UnknownPreimage.Hash() {
|
||||
return fmt.Errorf("cannot use hash of all-zeroes preimage")
|
||||
}
|
||||
|
||||
if len(i.Memo) > MaxMemoSize {
|
||||
return fmt.Errorf("max length a memo is %v, and invoice "+
|
||||
"of length %v was provided", MaxMemoSize, len(i.Memo))
|
||||
}
|
||||
if len(i.PaymentRequest) > MaxPaymentRequestSize {
|
||||
return fmt.Errorf("max length of payment request is %v, "+
|
||||
"length provided was %v", MaxPaymentRequestSize,
|
||||
len(i.PaymentRequest))
|
||||
}
|
||||
if i.Terms.Features == nil {
|
||||
return errors.New("invoice must have a feature vector")
|
||||
}
|
||||
|
||||
err := feature.ValidateDeps(i.Terms.Features)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// AMP invoices and hodl invoices are allowed to have no preimage
|
||||
// specified.
|
||||
isAMP := i.Terms.Features.HasFeature(
|
||||
lnwire.AMPOptional,
|
||||
)
|
||||
if i.Terms.PaymentPreimage == nil && !(i.HodlInvoice || isAMP) {
|
||||
return errors.New("non-hodl invoices must have a preimage")
|
||||
}
|
||||
|
||||
if len(i.Htlcs) > 0 {
|
||||
return ErrInvoiceHasHtlcs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPending returns true if the invoice is in ContractOpen state.
|
||||
func (i *Invoice) IsPending() bool {
|
||||
return i.State == ContractOpen || i.State == ContractAccepted
|
||||
}
|
||||
|
||||
// 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, error) {
|
||||
dest := Invoice{
|
||||
Memo: copySlice(src.Memo),
|
||||
PaymentRequest: copySlice(src.PaymentRequest),
|
||||
CreationDate: src.CreationDate,
|
||||
SettleDate: src.SettleDate,
|
||||
Terms: src.Terms,
|
||||
AddIndex: src.AddIndex,
|
||||
SettleIndex: src.SettleIndex,
|
||||
State: src.State,
|
||||
AmtPaid: src.AmtPaid,
|
||||
Htlcs: make(
|
||||
map[CircuitKey]*InvoiceHTLC, len(src.Htlcs),
|
||||
),
|
||||
AMPState: make(map[SetID]InvoiceStateAMP),
|
||||
HodlInvoice: src.HodlInvoice,
|
||||
}
|
||||
|
||||
dest.Terms.Features = src.Terms.Features.Clone()
|
||||
|
||||
if src.Terms.PaymentPreimage != nil {
|
||||
preimage := *src.Terms.PaymentPreimage
|
||||
dest.Terms.PaymentPreimage = &preimage
|
||||
}
|
||||
|
||||
for k, v := range src.Htlcs {
|
||||
dest.Htlcs[k] = v.Copy()
|
||||
}
|
||||
|
||||
// Lastly, copy the amp invoice state.
|
||||
for k, v := range src.AMPState {
|
||||
ampInvState, err := v.copy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dest.AMPState[k] = ampInvState
|
||||
}
|
||||
|
||||
return &dest, nil
|
||||
}
|
||||
|
||||
// InvoiceDeleteRef holds a reference to an invoice to be deleted.
|
||||
type InvoiceDeleteRef struct {
|
||||
// PayHash is the payment hash of the target invoice. All invoices are
|
||||
// currently indexed by payment hash.
|
||||
PayHash lntypes.Hash
|
||||
|
||||
// PayAddr is the payment addr of the target invoice. Newer invoices
|
||||
// (0.11 and up) are indexed by payment address in addition to payment
|
||||
// hash, but pre 0.8 invoices do not have one at all.
|
||||
PayAddr *[32]byte
|
||||
|
||||
// AddIndex is the add index of the invoice.
|
||||
AddIndex uint64
|
||||
|
||||
// SettleIndex is the settle index of the invoice.
|
||||
SettleIndex uint64
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockInvoiceDB struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func NewInvoicesDBMock() *MockInvoiceDB {
|
||||
return &MockInvoiceDB{}
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) AddInvoice(invoice *Invoice,
|
||||
paymentHash lntypes.Hash) (uint64, error) {
|
||||
|
||||
args := m.Called(invoice, paymentHash)
|
||||
|
||||
addIndex, _ := args.Get(0).(uint64)
|
||||
|
||||
// NOTE: this is a side effect of the AddInvoice method.
|
||||
invoice.AddIndex = addIndex
|
||||
|
||||
return addIndex, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) InvoicesAddedSince(idx uint64) ([]Invoice, error) {
|
||||
args := m.Called(idx)
|
||||
invoices, _ := args.Get(0).([]Invoice)
|
||||
|
||||
return invoices, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) InvoicesSettledSince(idx uint64) ([]Invoice, error) {
|
||||
args := m.Called(idx)
|
||||
invoices, _ := args.Get(0).([]Invoice)
|
||||
|
||||
return invoices, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) LookupInvoice(ref InvoiceRef) (Invoice, error) {
|
||||
args := m.Called(ref)
|
||||
invoice, _ := args.Get(0).(Invoice)
|
||||
|
||||
return invoice, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) ScanInvoices(scanFunc InvScanFunc,
|
||||
reset func()) error {
|
||||
|
||||
args := m.Called(scanFunc, reset)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
|
||||
args := m.Called(q)
|
||||
invoiceSlice, _ := args.Get(0).(InvoiceSlice)
|
||||
|
||||
return invoiceSlice, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) UpdateInvoice(ref InvoiceRef, setIDHint *SetID,
|
||||
callback InvoiceUpdateCallback) (*Invoice, error) {
|
||||
|
||||
args := m.Called(ref, setIDHint, callback)
|
||||
invoice, _ := args.Get(0).(*Invoice)
|
||||
|
||||
return invoice, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockInvoiceDB) DeleteInvoice(invoices []InvoiceDeleteRef) error {
|
||||
args := m.Called(invoices)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
+11
-12
@@ -3,7 +3,6 @@ package invoices
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
)
|
||||
|
||||
@@ -11,14 +10,14 @@ import (
|
||||
type HtlcResolution interface {
|
||||
// CircuitKey returns the circuit key for the htlc that we have a
|
||||
// resolution for.
|
||||
CircuitKey() models.CircuitKey
|
||||
CircuitKey() CircuitKey
|
||||
}
|
||||
|
||||
// HtlcFailResolution is an implementation of the HtlcResolution interface
|
||||
// which is returned when a htlc is failed.
|
||||
type HtlcFailResolution struct {
|
||||
// circuitKey is the key of the htlc for which we have a resolution.
|
||||
circuitKey models.CircuitKey
|
||||
circuitKey CircuitKey
|
||||
|
||||
// AcceptHeight is the original height at which the htlc was accepted.
|
||||
AcceptHeight int32
|
||||
@@ -28,7 +27,7 @@ type HtlcFailResolution struct {
|
||||
}
|
||||
|
||||
// NewFailResolution returns a htlc failure resolution.
|
||||
func NewFailResolution(key models.CircuitKey, acceptHeight int32,
|
||||
func NewFailResolution(key CircuitKey, acceptHeight int32,
|
||||
outcome FailResolutionResult) *HtlcFailResolution {
|
||||
|
||||
return &HtlcFailResolution{
|
||||
@@ -42,7 +41,7 @@ func NewFailResolution(key models.CircuitKey, acceptHeight int32,
|
||||
// resolution for.
|
||||
//
|
||||
// Note: it is part of the HtlcResolution interface.
|
||||
func (f *HtlcFailResolution) CircuitKey() models.CircuitKey {
|
||||
func (f *HtlcFailResolution) CircuitKey() CircuitKey {
|
||||
return f.circuitKey
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ type HtlcSettleResolution struct {
|
||||
Preimage lntypes.Preimage
|
||||
|
||||
// circuitKey is the key of the htlc for which we have a resolution.
|
||||
circuitKey models.CircuitKey
|
||||
circuitKey CircuitKey
|
||||
|
||||
// acceptHeight is the original height at which the htlc was accepted.
|
||||
AcceptHeight int32
|
||||
@@ -64,8 +63,8 @@ type HtlcSettleResolution struct {
|
||||
|
||||
// NewSettleResolution returns a htlc resolution which is associated with a
|
||||
// settle.
|
||||
func NewSettleResolution(preimage lntypes.Preimage,
|
||||
key models.CircuitKey, acceptHeight int32,
|
||||
func NewSettleResolution(preimage lntypes.Preimage, key CircuitKey,
|
||||
acceptHeight int32,
|
||||
outcome SettleResolutionResult) *HtlcSettleResolution {
|
||||
|
||||
return &HtlcSettleResolution{
|
||||
@@ -80,7 +79,7 @@ func NewSettleResolution(preimage lntypes.Preimage,
|
||||
// resolution for.
|
||||
//
|
||||
// Note: it is part of the HtlcResolution interface.
|
||||
func (s *HtlcSettleResolution) CircuitKey() models.CircuitKey {
|
||||
func (s *HtlcSettleResolution) CircuitKey() CircuitKey {
|
||||
return s.circuitKey
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ func (s *HtlcSettleResolution) CircuitKey() models.CircuitKey {
|
||||
// acceptResolution, a nil resolution should be surfaced.
|
||||
type htlcAcceptResolution struct {
|
||||
// circuitKey is the key of the htlc for which we have a resolution.
|
||||
circuitKey models.CircuitKey
|
||||
circuitKey CircuitKey
|
||||
|
||||
// autoRelease signals that the htlc should be automatically released
|
||||
// after a timeout.
|
||||
@@ -107,7 +106,7 @@ type htlcAcceptResolution struct {
|
||||
|
||||
// newAcceptResolution returns a htlc resolution which is associated with a
|
||||
// htlc accept.
|
||||
func newAcceptResolution(key models.CircuitKey,
|
||||
func newAcceptResolution(key CircuitKey,
|
||||
outcome acceptResolutionResult) *htlcAcceptResolution {
|
||||
|
||||
return &htlcAcceptResolution{
|
||||
@@ -120,6 +119,6 @@ func newAcceptResolution(key models.CircuitKey,
|
||||
// resolution for.
|
||||
//
|
||||
// Note: it is part of the HtlcResolution interface.
|
||||
func (a *htlcAcceptResolution) CircuitKey() models.CircuitKey {
|
||||
func (a *htlcAcceptResolution) CircuitKey() CircuitKey {
|
||||
return a.circuitKey
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockChainNotifier struct {
|
||||
chainntnfs.ChainNotifier
|
||||
|
||||
blockChan chan *chainntnfs.BlockEpoch
|
||||
}
|
||||
|
||||
func newMockNotifier() *mockChainNotifier {
|
||||
return &mockChainNotifier{
|
||||
blockChan: make(chan *chainntnfs.BlockEpoch),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn mocks a block epoch notification, using the mock's
|
||||
// block channel to deliver blocks to the client.
|
||||
func (m *mockChainNotifier) RegisterBlockEpochNtfn(*chainntnfs.BlockEpoch) (
|
||||
*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
return &chainntnfs.BlockEpochEvent{
|
||||
Epochs: m.blockChan,
|
||||
Cancel: func() {},
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
testCurrentHeight = int32(1)
|
||||
)
|
||||
|
||||
var (
|
||||
testTimeout = 5 * time.Second
|
||||
|
||||
testTime = time.Date(2018, time.February, 2, 14, 0, 0, 0, time.UTC)
|
||||
|
||||
testInvoicePreimage = lntypes.Preimage{1}
|
||||
|
||||
testPrivKeyBytes, _ = hex.DecodeString(
|
||||
"e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2d" +
|
||||
"b734",
|
||||
)
|
||||
|
||||
testPrivKey, _ = btcec.PrivKeyFromBytes(testPrivKeyBytes)
|
||||
|
||||
testInvoiceDescription = "coffee"
|
||||
|
||||
testInvoiceAmount = lnwire.MilliSatoshi(100000) //nolint:gomnd
|
||||
|
||||
testNetParams = &chaincfg.MainNetParams
|
||||
|
||||
testMessageSigner = zpay32.MessageSigner{
|
||||
SignCompact: func(msg []byte) ([]byte, error) {
|
||||
hash := chainhash.HashB(msg)
|
||||
sig, err := ecdsa.SignCompact(testPrivKey, hash, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sign the "+
|
||||
"message: %v", err)
|
||||
}
|
||||
|
||||
return sig, nil
|
||||
},
|
||||
}
|
||||
|
||||
testFeatures = lnwire.NewFeatureVector(
|
||||
nil, lnwire.Features,
|
||||
)
|
||||
)
|
||||
|
||||
func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
||||
timestamp time.Time, expiry time.Duration) *Invoice {
|
||||
|
||||
t.Helper()
|
||||
|
||||
if expiry == 0 {
|
||||
expiry = time.Hour
|
||||
}
|
||||
|
||||
var payAddr [32]byte
|
||||
if _, err := rand.Read(payAddr[:]); err != nil {
|
||||
t.Fatalf("unable to generate payment addr: %v", err)
|
||||
}
|
||||
|
||||
rawInvoice, err := zpay32.NewInvoice(
|
||||
testNetParams,
|
||||
preimage.Hash(),
|
||||
timestamp,
|
||||
zpay32.Amount(testInvoiceAmount),
|
||||
zpay32.Description(testInvoiceDescription),
|
||||
zpay32.Expiry(expiry),
|
||||
zpay32.PaymentAddr(payAddr),
|
||||
)
|
||||
require.NoError(t, err, "Error while creating new invoice")
|
||||
|
||||
paymentRequest, err := rawInvoice.Encode(testMessageSigner)
|
||||
|
||||
require.NoError(t, err, "Error while encoding payment request")
|
||||
|
||||
return &Invoice{
|
||||
Terms: ContractTerm{
|
||||
PaymentPreimage: &preimage,
|
||||
PaymentAddr: payAddr,
|
||||
Value: testInvoiceAmount,
|
||||
Expiry: expiry,
|
||||
Features: testFeatures,
|
||||
},
|
||||
PaymentRequest: []byte(paymentRequest),
|
||||
CreationDate: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
// invoiceExpiryTestData simply holds generated expired and pending invoices.
|
||||
type invoiceExpiryTestData struct {
|
||||
expiredInvoices map[lntypes.Hash]*Invoice
|
||||
pendingInvoices map[lntypes.Hash]*Invoice
|
||||
}
|
||||
|
||||
// generateInvoiceExpiryTestData generates the specified number of fake expired
|
||||
// and pending invoices anchored to the passed now timestamp.
|
||||
func generateInvoiceExpiryTestData(
|
||||
t *testing.T, now time.Time,
|
||||
offset, numExpired, numPending int) invoiceExpiryTestData {
|
||||
|
||||
t.Helper()
|
||||
|
||||
var testData invoiceExpiryTestData
|
||||
|
||||
testData.expiredInvoices = make(map[lntypes.Hash]*Invoice)
|
||||
testData.pendingInvoices = make(map[lntypes.Hash]*Invoice)
|
||||
|
||||
expiredCreationDate := now.Add(-24 * time.Hour)
|
||||
|
||||
for i := 1; i <= numExpired; i++ {
|
||||
var preimage lntypes.Preimage
|
||||
binary.BigEndian.PutUint32(preimage[:4], uint32(offset+i))
|
||||
duration := (i + offset) % 24 //nolint:gomnd
|
||||
expiry := time.Duration(duration) * time.Hour
|
||||
invoice := newTestInvoice(
|
||||
t, preimage, expiredCreationDate, expiry,
|
||||
)
|
||||
testData.expiredInvoices[preimage.Hash()] = invoice
|
||||
}
|
||||
|
||||
for i := 1; i <= numPending; i++ {
|
||||
var preimage lntypes.Preimage
|
||||
binary.BigEndian.PutUint32(preimage[4:], uint32(offset+i))
|
||||
duration := (i + offset) % 24 //nolint:gomnd
|
||||
expiry := time.Duration(duration) * time.Hour
|
||||
invoice := newTestInvoice(t, preimage, now, expiry)
|
||||
testData.pendingInvoices[preimage.Hash()] = invoice
|
||||
}
|
||||
|
||||
return testData
|
||||
}
|
||||
|
||||
type hodlExpiryTest struct {
|
||||
hash lntypes.Hash
|
||||
state ContractState
|
||||
stateLock sync.Mutex
|
||||
mockNotifier *mockChainNotifier
|
||||
mockClock *clock.TestClock
|
||||
cancelChan chan lntypes.Hash
|
||||
watcher *InvoiceExpiryWatcher
|
||||
}
|
||||
|
||||
func (h *hodlExpiryTest) setState(state ContractState) {
|
||||
h.stateLock.Lock()
|
||||
defer h.stateLock.Unlock()
|
||||
|
||||
h.state = state
|
||||
}
|
||||
|
||||
func (h *hodlExpiryTest) announceBlock(t *testing.T, height uint32) {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case h.mockNotifier.blockChan <- &chainntnfs.BlockEpoch{
|
||||
Height: int32(height),
|
||||
}:
|
||||
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatalf("block %v not consumed", height)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hodlExpiryTest) assertCanceled(t *testing.T, expected lntypes.Hash) {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case actual := <-h.cancelChan:
|
||||
require.Equal(t, expected, actual)
|
||||
|
||||
case <-time.After(testTimeout):
|
||||
t.Fatalf("invoice: %v not canceled", h.hash)
|
||||
}
|
||||
}
|
||||
|
||||
// setupHodlExpiry creates a hodl invoice in our expiry watcher and runs an
|
||||
// arbitrary update function which advances the invoices's state.
|
||||
func setupHodlExpiry(t *testing.T, creationDate time.Time,
|
||||
expiry time.Duration, heightDelta uint32,
|
||||
startState ContractState,
|
||||
startHtlcs []*InvoiceHTLC) *hodlExpiryTest {
|
||||
|
||||
t.Helper()
|
||||
|
||||
mockNotifier := newMockNotifier()
|
||||
mockClock := clock.NewTestClock(testTime)
|
||||
|
||||
test := &hodlExpiryTest{
|
||||
state: startState,
|
||||
watcher: NewInvoiceExpiryWatcher(
|
||||
mockClock, heightDelta, uint32(testCurrentHeight), nil,
|
||||
mockNotifier,
|
||||
),
|
||||
cancelChan: make(chan lntypes.Hash),
|
||||
mockNotifier: mockNotifier,
|
||||
mockClock: mockClock,
|
||||
}
|
||||
|
||||
// Use an unbuffered channel to block on cancel calls so that the test
|
||||
// does not exit before we've processed all the invoices we expect.
|
||||
cancelImpl := func(paymentHash lntypes.Hash, force bool) error {
|
||||
test.stateLock.Lock()
|
||||
currentState := test.state
|
||||
test.stateLock.Unlock()
|
||||
|
||||
if currentState != ContractOpen && !force {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case test.cancelChan <- paymentHash:
|
||||
case <-time.After(testTimeout):
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
require.NoError(t, test.watcher.Start(cancelImpl))
|
||||
|
||||
// We set preimage and hash so that we can use our existing test
|
||||
// helpers. In practice we would only have the hash, but this does not
|
||||
// affect what we're testing at all.
|
||||
preimage := lntypes.Preimage{1}
|
||||
test.hash = preimage.Hash()
|
||||
|
||||
invoice := newTestInvoice(t, preimage, creationDate, expiry)
|
||||
invoice.State = startState
|
||||
invoice.HodlInvoice = true
|
||||
invoice.Htlcs = make(map[CircuitKey]*InvoiceHTLC)
|
||||
|
||||
// If we have any htlcs, add them with unique circult keys.
|
||||
for i, htlc := range startHtlcs {
|
||||
key := CircuitKey{
|
||||
HtlcID: uint64(i),
|
||||
}
|
||||
|
||||
invoice.Htlcs[key] = htlc
|
||||
}
|
||||
|
||||
// Create an expiry entry for our invoice in its starting state. This
|
||||
// mimics adding invoices to the watcher on start.
|
||||
entry := makeInvoiceExpiry(test.hash, invoice)
|
||||
test.watcher.AddInvoices(entry)
|
||||
|
||||
return test
|
||||
}
|
||||
+85
-119
@@ -1,4 +1,4 @@
|
||||
package invoices
|
||||
package invoices_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
invpkg "github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
@@ -55,6 +55,29 @@ func (p *mockPayload) Metadata() []byte {
|
||||
return p.metadata
|
||||
}
|
||||
|
||||
type mockChainNotifier struct {
|
||||
chainntnfs.ChainNotifier
|
||||
|
||||
blockChan chan *chainntnfs.BlockEpoch
|
||||
}
|
||||
|
||||
func newMockNotifier() *mockChainNotifier {
|
||||
return &mockChainNotifier{
|
||||
blockChan: make(chan *chainntnfs.BlockEpoch),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterBlockEpochNtfn mocks a block epoch notification, using the mock's
|
||||
// block channel to deliver blocks to the client.
|
||||
func (m *mockChainNotifier) RegisterBlockEpochNtfn(*chainntnfs.BlockEpoch) (
|
||||
*chainntnfs.BlockEpochEvent, error) {
|
||||
|
||||
return &chainntnfs.BlockEpochEvent{
|
||||
Epochs: m.blockChan,
|
||||
Cancel: func() {},
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
testHtlcExpiry = uint32(5)
|
||||
|
||||
@@ -92,7 +115,8 @@ var (
|
||||
hash := chainhash.HashB(msg)
|
||||
sig, err := ecdsa.SignCompact(testPrivKey, hash, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sign the message: %v", err)
|
||||
return nil, fmt.Errorf("can't sign the "+
|
||||
"message: %v", err)
|
||||
}
|
||||
return sig, nil
|
||||
},
|
||||
@@ -109,8 +133,8 @@ var (
|
||||
|
||||
var (
|
||||
testInvoiceAmt = lnwire.MilliSatoshi(100000)
|
||||
testInvoice = &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
testInvoice = &invpkg.Invoice{
|
||||
Terms: invpkg.ContractTerm{
|
||||
PaymentPreimage: &testInvoicePreimage,
|
||||
Value: testInvoiceAmt,
|
||||
Expiry: time.Hour,
|
||||
@@ -119,8 +143,8 @@ var (
|
||||
CreationDate: testInvoiceCreationDate,
|
||||
}
|
||||
|
||||
testPayAddrReqInvoice = &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
testPayAddrReqInvoice = &invpkg.Invoice{
|
||||
Terms: invpkg.ContractTerm{
|
||||
PaymentPreimage: &testInvoicePreimage,
|
||||
Value: testInvoiceAmt,
|
||||
Expiry: time.Hour,
|
||||
@@ -135,8 +159,8 @@ var (
|
||||
CreationDate: testInvoiceCreationDate,
|
||||
}
|
||||
|
||||
testPayAddrOptionalInvoice = &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
testPayAddrOptionalInvoice = &invpkg.Invoice{
|
||||
Terms: invpkg.ContractTerm{
|
||||
PaymentPreimage: &testInvoicePreimage,
|
||||
Value: testInvoiceAmt,
|
||||
Expiry: time.Hour,
|
||||
@@ -151,8 +175,8 @@ var (
|
||||
CreationDate: testInvoiceCreationDate,
|
||||
}
|
||||
|
||||
testHodlInvoice = &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
testHodlInvoice = &invpkg.Invoice{
|
||||
Terms: invpkg.ContractTerm{
|
||||
Value: testInvoiceAmt,
|
||||
Expiry: time.Hour,
|
||||
Features: testFeatures,
|
||||
@@ -163,6 +187,8 @@ var (
|
||||
)
|
||||
|
||||
func newTestChannelDB(t *testing.T, clock clock.Clock) (*channeldb.DB, error) {
|
||||
t.Helper()
|
||||
|
||||
// Create channeldb for the first time.
|
||||
cdb, err := channeldb.Open(
|
||||
t.TempDir(), channeldb.OptionClock(clock),
|
||||
@@ -179,35 +205,47 @@ func newTestChannelDB(t *testing.T, clock clock.Clock) (*channeldb.DB, error) {
|
||||
}
|
||||
|
||||
type testContext struct {
|
||||
cdb *channeldb.DB
|
||||
registry *InvoiceRegistry
|
||||
idb *channeldb.DB
|
||||
registry *invpkg.InvoiceRegistry
|
||||
notifier *mockChainNotifier
|
||||
clock *clock.TestClock
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func newTestContext(t *testing.T) *testContext {
|
||||
func defaultRegistryConfig() invpkg.RegistryConfig {
|
||||
return invpkg.RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
HtlcHoldDuration: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestContext(t *testing.T,
|
||||
registryCfg *invpkg.RegistryConfig) *testContext {
|
||||
|
||||
t.Helper()
|
||||
|
||||
clock := clock.NewTestClock(testTime)
|
||||
|
||||
cdb, err := newTestChannelDB(t, clock)
|
||||
idb, err := newTestChannelDB(t, clock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
notifier := newMockNotifier()
|
||||
|
||||
expiryWatcher := NewInvoiceExpiryWatcher(
|
||||
expiryWatcher := invpkg.NewInvoiceExpiryWatcher(
|
||||
clock, 0, uint32(testCurrentHeight), nil, notifier,
|
||||
)
|
||||
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
cfg := RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
HtlcHoldDuration: 30 * time.Second,
|
||||
Clock: clock,
|
||||
cfg := defaultRegistryConfig()
|
||||
if registryCfg != nil {
|
||||
cfg = *registryCfg
|
||||
}
|
||||
registry := NewRegistry(cdb, expiryWatcher, &cfg)
|
||||
cfg.Clock = clock
|
||||
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
registry := invpkg.NewRegistry(idb, expiryWatcher, &cfg)
|
||||
|
||||
err = registry.Start()
|
||||
if err != nil {
|
||||
@@ -218,7 +256,7 @@ func newTestContext(t *testing.T) *testContext {
|
||||
})
|
||||
|
||||
ctx := testContext{
|
||||
cdb: cdb,
|
||||
idb: idb,
|
||||
registry: registry,
|
||||
notifier: notifier,
|
||||
clock: clock,
|
||||
@@ -228,8 +266,8 @@ func newTestContext(t *testing.T) *testContext {
|
||||
return &ctx
|
||||
}
|
||||
|
||||
func getCircuitKey(htlcID uint64) models.CircuitKey {
|
||||
return models.CircuitKey{
|
||||
func getCircuitKey(htlcID uint64) invpkg.CircuitKey {
|
||||
return invpkg.CircuitKey{
|
||||
ChanID: lnwire.ShortChannelID{
|
||||
BlockHeight: 1, TxIndex: 2, TxPosition: 3,
|
||||
},
|
||||
@@ -238,7 +276,7 @@ func getCircuitKey(htlcID uint64) models.CircuitKey {
|
||||
}
|
||||
|
||||
func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
||||
timestamp time.Time, expiry time.Duration) *channeldb.Invoice {
|
||||
timestamp time.Time, expiry time.Duration) *invpkg.Invoice {
|
||||
|
||||
if expiry == 0 {
|
||||
expiry = time.Hour
|
||||
@@ -264,8 +302,8 @@ func newTestInvoice(t *testing.T, preimage lntypes.Preimage,
|
||||
|
||||
require.NoError(t, err, "Error while encoding payment request")
|
||||
|
||||
return &channeldb.Invoice{
|
||||
Terms: channeldb.ContractTerm{
|
||||
return &invpkg.Invoice{
|
||||
Terms: invpkg.ContractTerm{
|
||||
PaymentPreimage: &preimage,
|
||||
PaymentAddr: payAddr,
|
||||
Value: testInvoiceAmount,
|
||||
@@ -286,7 +324,8 @@ func timeout() func() {
|
||||
case <-time.After(5 * time.Second):
|
||||
err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error writing to std out after timeout: %v", err))
|
||||
panic(fmt.Sprintf("error writing to std out "+
|
||||
"after timeout: %v", err))
|
||||
}
|
||||
panic("timeout")
|
||||
case <-done:
|
||||
@@ -300,8 +339,8 @@ func timeout() func() {
|
||||
|
||||
// invoiceExpiryTestData simply holds generated expired and pending invoices.
|
||||
type invoiceExpiryTestData struct {
|
||||
expiredInvoices map[lntypes.Hash]*channeldb.Invoice
|
||||
pendingInvoices map[lntypes.Hash]*channeldb.Invoice
|
||||
expiredInvoices map[lntypes.Hash]*invpkg.Invoice
|
||||
pendingInvoices map[lntypes.Hash]*invpkg.Invoice
|
||||
}
|
||||
|
||||
// generateInvoiceExpiryTestData generates the specified number of fake expired
|
||||
@@ -312,8 +351,8 @@ func generateInvoiceExpiryTestData(
|
||||
|
||||
var testData invoiceExpiryTestData
|
||||
|
||||
testData.expiredInvoices = make(map[lntypes.Hash]*channeldb.Invoice)
|
||||
testData.pendingInvoices = make(map[lntypes.Hash]*channeldb.Invoice)
|
||||
testData.expiredInvoices = make(map[lntypes.Hash]*invpkg.Invoice)
|
||||
testData.pendingInvoices = make(map[lntypes.Hash]*invpkg.Invoice)
|
||||
|
||||
expiredCreationDate := now.Add(-24 * time.Hour)
|
||||
|
||||
@@ -321,7 +360,9 @@ func generateInvoiceExpiryTestData(
|
||||
var preimage lntypes.Preimage
|
||||
binary.BigEndian.PutUint32(preimage[:4], uint32(offset+i))
|
||||
expiry := time.Duration((i+offset)%24) * time.Hour
|
||||
invoice := newTestInvoice(t, preimage, expiredCreationDate, expiry)
|
||||
invoice := newTestInvoice(
|
||||
t, preimage, expiredCreationDate, expiry,
|
||||
)
|
||||
testData.expiredInvoices[preimage.Hash()] = invoice
|
||||
}
|
||||
|
||||
@@ -339,12 +380,12 @@ func generateInvoiceExpiryTestData(
|
||||
// checkSettleResolution asserts the resolution is a settle with the correct
|
||||
// preimage. If successful, the HtlcSettleResolution is returned in case further
|
||||
// checks are desired.
|
||||
func checkSettleResolution(t *testing.T, res HtlcResolution,
|
||||
expPreimage lntypes.Preimage) *HtlcSettleResolution {
|
||||
func checkSettleResolution(t *testing.T, res invpkg.HtlcResolution,
|
||||
expPreimage lntypes.Preimage) *invpkg.HtlcSettleResolution {
|
||||
|
||||
t.Helper()
|
||||
|
||||
settleResolution, ok := res.(*HtlcSettleResolution)
|
||||
settleResolution, ok := res.(*invpkg.HtlcSettleResolution)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expPreimage, settleResolution.Preimage)
|
||||
|
||||
@@ -354,11 +395,11 @@ func checkSettleResolution(t *testing.T, res HtlcResolution,
|
||||
// checkFailResolution asserts the resolution is a fail with the correct reason.
|
||||
// If successful, the HtlcFailResolution is returned in case further checks are
|
||||
// desired.
|
||||
func checkFailResolution(t *testing.T, res HtlcResolution,
|
||||
expOutcome FailResolutionResult) *HtlcFailResolution {
|
||||
func checkFailResolution(t *testing.T, res invpkg.HtlcResolution,
|
||||
expOutcome invpkg.FailResolutionResult) *invpkg.HtlcFailResolution {
|
||||
|
||||
t.Helper()
|
||||
failResolution, ok := res.(*HtlcFailResolution)
|
||||
failResolution, ok := res.(*invpkg.HtlcFailResolution)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expOutcome, failResolution.Outcome)
|
||||
|
||||
@@ -367,22 +408,17 @@ func checkFailResolution(t *testing.T, res HtlcResolution,
|
||||
|
||||
type hodlExpiryTest struct {
|
||||
hash lntypes.Hash
|
||||
state channeldb.ContractState
|
||||
state invpkg.ContractState
|
||||
stateLock sync.Mutex
|
||||
mockNotifier *mockChainNotifier
|
||||
mockClock *clock.TestClock
|
||||
cancelChan chan lntypes.Hash
|
||||
watcher *InvoiceExpiryWatcher
|
||||
}
|
||||
|
||||
func (h *hodlExpiryTest) setState(state channeldb.ContractState) {
|
||||
h.stateLock.Lock()
|
||||
defer h.stateLock.Unlock()
|
||||
|
||||
h.state = state
|
||||
watcher *invpkg.InvoiceExpiryWatcher
|
||||
}
|
||||
|
||||
func (h *hodlExpiryTest) announceBlock(t *testing.T, height uint32) {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case h.mockNotifier.blockChan <- &chainntnfs.BlockEpoch{
|
||||
Height: int32(height),
|
||||
@@ -402,73 +438,3 @@ func (h *hodlExpiryTest) assertCanceled(t *testing.T, expected lntypes.Hash) {
|
||||
t.Fatalf("invoice: %v not canceled", h.hash)
|
||||
}
|
||||
}
|
||||
|
||||
// setupHodlExpiry creates a hodl invoice in our expiry watcher and runs an
|
||||
// arbitrary update function which advances the invoices's state.
|
||||
func setupHodlExpiry(t *testing.T, creationDate time.Time,
|
||||
expiry time.Duration, heightDelta uint32,
|
||||
startState channeldb.ContractState,
|
||||
startHtlcs []*channeldb.InvoiceHTLC) *hodlExpiryTest {
|
||||
|
||||
mockNotifier := newMockNotifier()
|
||||
mockClock := clock.NewTestClock(testTime)
|
||||
|
||||
test := &hodlExpiryTest{
|
||||
state: startState,
|
||||
watcher: NewInvoiceExpiryWatcher(
|
||||
mockClock, heightDelta, uint32(testCurrentHeight), nil,
|
||||
mockNotifier,
|
||||
),
|
||||
cancelChan: make(chan lntypes.Hash),
|
||||
mockNotifier: mockNotifier,
|
||||
mockClock: mockClock,
|
||||
}
|
||||
|
||||
// Use an unbuffered channel to block on cancel calls so that the test
|
||||
// does not exit before we've processed all the invoices we expect.
|
||||
cancelImpl := func(paymentHash lntypes.Hash, force bool) error {
|
||||
test.stateLock.Lock()
|
||||
currentState := test.state
|
||||
test.stateLock.Unlock()
|
||||
|
||||
if currentState != channeldb.ContractOpen && !force {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case test.cancelChan <- paymentHash:
|
||||
case <-time.After(testTimeout):
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
require.NoError(t, test.watcher.Start(cancelImpl))
|
||||
|
||||
// We set preimage and hash so that we can use our existing test
|
||||
// helpers. In practice we would only have the hash, but this does not
|
||||
// affect what we're testing at all.
|
||||
preimage := lntypes.Preimage{1}
|
||||
test.hash = preimage.Hash()
|
||||
|
||||
invoice := newTestInvoice(t, preimage, creationDate, expiry)
|
||||
invoice.State = startState
|
||||
invoice.HodlInvoice = true
|
||||
invoice.Htlcs = make(map[models.CircuitKey]*channeldb.InvoiceHTLC)
|
||||
|
||||
// If we have any htlcs, add them with unique circult keys.
|
||||
for i, htlc := range startHtlcs {
|
||||
key := models.CircuitKey{
|
||||
HtlcID: uint64(i),
|
||||
}
|
||||
|
||||
invoice.Htlcs[key] = htlc
|
||||
}
|
||||
|
||||
// Create an expiry entry for our invoice in its starting state. This
|
||||
// mimics adding invoices to the watcher on start.
|
||||
entry := makeInvoiceExpiry(test.hash, invoice)
|
||||
test.watcher.AddInvoices(entry)
|
||||
|
||||
return test
|
||||
}
|
||||
+42
-43
@@ -5,8 +5,6 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/lightningnetwork/lnd/amp"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
@@ -16,7 +14,7 @@ import (
|
||||
// update to be carried out.
|
||||
type invoiceUpdateCtx struct {
|
||||
hash lntypes.Hash
|
||||
circuitKey models.CircuitKey
|
||||
circuitKey CircuitKey
|
||||
amtPaid lnwire.MilliSatoshi
|
||||
expiry uint32
|
||||
currentHeight int32
|
||||
@@ -29,16 +27,18 @@ type invoiceUpdateCtx struct {
|
||||
|
||||
// invoiceRef returns an identifier that can be used to lookup or update the
|
||||
// invoice this HTLC is targeting.
|
||||
func (i *invoiceUpdateCtx) invoiceRef() channeldb.InvoiceRef {
|
||||
func (i *invoiceUpdateCtx) invoiceRef() InvoiceRef {
|
||||
switch {
|
||||
case i.amp != nil && i.mpp != nil:
|
||||
payAddr := i.mpp.PaymentAddr()
|
||||
return channeldb.InvoiceRefByAddr(payAddr)
|
||||
return InvoiceRefByAddr(payAddr)
|
||||
|
||||
case i.mpp != nil:
|
||||
payAddr := i.mpp.PaymentAddr()
|
||||
return channeldb.InvoiceRefByHashAndAddr(i.hash, payAddr)
|
||||
return InvoiceRefByHashAndAddr(i.hash, payAddr)
|
||||
|
||||
default:
|
||||
return channeldb.InvoiceRefByHash(i.hash)
|
||||
return InvoiceRefByHash(i.hash)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,20 +95,20 @@ func (i invoiceUpdateCtx) acceptRes(outcome acceptResolutionResult) *htlcAcceptR
|
||||
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
|
||||
// settlement logic. It returns a hltc resolution that indicates what the
|
||||
// outcome of the update was.
|
||||
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
func updateInvoice(ctx *invoiceUpdateCtx, inv *Invoice) (
|
||||
*InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
|
||||
// Don't update the invoice when this is a replayed htlc.
|
||||
htlc, ok := inv.Htlcs[ctx.circuitKey]
|
||||
if ok {
|
||||
switch htlc.State {
|
||||
case channeldb.HtlcStateCanceled:
|
||||
case HtlcStateCanceled:
|
||||
return nil, ctx.failRes(ResultReplayToCanceled), nil
|
||||
|
||||
case channeldb.HtlcStateAccepted:
|
||||
case HtlcStateAccepted:
|
||||
return nil, ctx.acceptRes(resultReplayToAccepted), nil
|
||||
|
||||
case channeldb.HtlcStateSettled:
|
||||
case HtlcStateSettled:
|
||||
pre := inv.Terms.PaymentPreimage
|
||||
|
||||
// Terms.PaymentPreimage will be nil for AMP invoices.
|
||||
@@ -139,8 +139,7 @@ func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
||||
|
||||
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
|
||||
// settlement logic for mpp payments.
|
||||
func updateMpp(ctx *invoiceUpdateCtx,
|
||||
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc,
|
||||
func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
|
||||
HtlcResolution, error) {
|
||||
|
||||
// Reject HTLCs to AMP invoices if they are missing an AMP payload, and
|
||||
@@ -160,7 +159,7 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
setID := ctx.setID()
|
||||
|
||||
// Start building the accept descriptor.
|
||||
acceptDesc := &channeldb.HtlcAcceptDesc{
|
||||
acceptDesc := &HtlcAcceptDesc{
|
||||
Amt: ctx.amtPaid,
|
||||
Expiry: ctx.expiry,
|
||||
AcceptHeight: ctx.currentHeight,
|
||||
@@ -169,7 +168,7 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
}
|
||||
|
||||
if ctx.amp != nil {
|
||||
acceptDesc.AMP = &channeldb.InvoiceHtlcAMPData{
|
||||
acceptDesc.AMP = &InvoiceHtlcAMPData{
|
||||
Record: *ctx.amp,
|
||||
Hash: ctx.hash,
|
||||
Preimage: nil,
|
||||
@@ -180,7 +179,7 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
// non-mpp payments that are accepted even after the invoice is settled.
|
||||
// Because non-mpp payments don't have a payment address, this is needed
|
||||
// to thwart probing.
|
||||
if inv.State != channeldb.ContractOpen {
|
||||
if inv.State != ContractOpen {
|
||||
return nil, ctx.failRes(ResultInvoiceNotOpen), nil
|
||||
}
|
||||
|
||||
@@ -200,7 +199,7 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
|
||||
}
|
||||
|
||||
htlcSet := inv.HTLCSet(setID, channeldb.HtlcStateAccepted)
|
||||
htlcSet := inv.HTLCSet(setID, HtlcStateAccepted)
|
||||
|
||||
// Check whether total amt matches other htlcs in the set.
|
||||
var newSetTotal lnwire.MilliSatoshi
|
||||
@@ -229,16 +228,16 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
||||
}
|
||||
|
||||
if setID != nil && *setID == channeldb.BlankPayAddr {
|
||||
if setID != nil && *setID == BlankPayAddr {
|
||||
return nil, ctx.failRes(ResultAmpError), nil
|
||||
}
|
||||
|
||||
// Record HTLC in the invoice database.
|
||||
newHtlcs := map[models.CircuitKey]*channeldb.HtlcAcceptDesc{
|
||||
newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
|
||||
ctx.circuitKey: acceptDesc,
|
||||
}
|
||||
|
||||
update := channeldb.InvoiceUpdateDesc{
|
||||
update := InvoiceUpdateDesc{
|
||||
AddHtlcs: newHtlcs,
|
||||
}
|
||||
|
||||
@@ -251,23 +250,23 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
// Check to see if we can settle or this is an hold invoice and
|
||||
// we need to wait for the preimage.
|
||||
if inv.HodlInvoice {
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractAccepted,
|
||||
update.State = &InvoiceStateUpdateDesc{
|
||||
NewState: ContractAccepted,
|
||||
SetID: setID,
|
||||
}
|
||||
return &update, ctx.acceptRes(resultAccepted), nil
|
||||
}
|
||||
|
||||
var (
|
||||
htlcPreimages map[models.CircuitKey]lntypes.Preimage
|
||||
htlcPreimages map[CircuitKey]lntypes.Preimage
|
||||
htlcPreimage lntypes.Preimage
|
||||
)
|
||||
if ctx.amp != nil {
|
||||
var failRes *HtlcFailResolution
|
||||
htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet)
|
||||
if failRes != nil {
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractCanceled,
|
||||
update.State = &InvoiceStateUpdateDesc{
|
||||
NewState: ContractCanceled,
|
||||
SetID: setID,
|
||||
}
|
||||
return &update, failRes, nil
|
||||
@@ -280,8 +279,8 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
htlcPreimage = *inv.Terms.PaymentPreimage
|
||||
}
|
||||
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractSettled,
|
||||
update.State = &InvoiceStateUpdateDesc{
|
||||
NewState: ContractSettled,
|
||||
Preimage: inv.Terms.PaymentPreimage,
|
||||
HTLCPreimages: htlcPreimages,
|
||||
SetID: setID,
|
||||
@@ -291,10 +290,10 @@ func updateMpp(ctx *invoiceUpdateCtx,
|
||||
}
|
||||
|
||||
// HTLCSet is a map of CircuitKey to InvoiceHTLC.
|
||||
type HTLCSet = map[models.CircuitKey]*channeldb.InvoiceHTLC
|
||||
type HTLCSet = map[CircuitKey]*InvoiceHTLC
|
||||
|
||||
// HTLCPreimages is a map of CircuitKey to preimage.
|
||||
type HTLCPreimages = map[models.CircuitKey]lntypes.Preimage
|
||||
type HTLCPreimages = map[CircuitKey]lntypes.Preimage
|
||||
|
||||
// reconstructAMPPreimages reconstructs the root seed for an AMP HTLC set and
|
||||
// verifies that all derived child hashes match the payment hashes of the HTLCs
|
||||
@@ -317,7 +316,7 @@ func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
|
||||
|
||||
// Next, construct an index mapping the position in childDescs to a
|
||||
// circuit key for all preexisting HTLCs.
|
||||
indexToCircuitKey := make(map[int]models.CircuitKey)
|
||||
indexToCircuitKey := make(map[int]CircuitKey)
|
||||
|
||||
// Add the child descriptor for each HTLC in the HTLC set, recording
|
||||
// it's position within the slice.
|
||||
@@ -351,7 +350,7 @@ func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
|
||||
// Finally, construct the map of learned preimages indexed by circuit
|
||||
// key, so that they can be persisted along with each HTLC when updating
|
||||
// the invoice.
|
||||
htlcPreimages := make(map[models.CircuitKey]lntypes.Preimage)
|
||||
htlcPreimages := make(map[CircuitKey]lntypes.Preimage)
|
||||
htlcPreimages[ctx.circuitKey] = children[0].Preimage
|
||||
for idx, child := range children[1:] {
|
||||
circuitKey := indexToCircuitKey[idx]
|
||||
@@ -368,11 +367,11 @@ func reconstructAMPPreimages(ctx *invoiceUpdateCtx,
|
||||
// send payments and any invoices we created in the past that are valid and
|
||||
// still had the optional mpp bit set.
|
||||
func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
inv *Invoice) (*InvoiceUpdateDesc, HtlcResolution, error) {
|
||||
|
||||
// If the invoice is already canceled, there is no further
|
||||
// checking to do.
|
||||
if inv.State == channeldb.ContractCanceled {
|
||||
if inv.State == ContractCanceled {
|
||||
return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
|
||||
}
|
||||
|
||||
@@ -402,7 +401,7 @@ func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
// Don't allow settling the invoice with an old style
|
||||
// htlc if we are already in the process of gathering an
|
||||
// mpp set.
|
||||
for _, htlc := range inv.HTLCSet(nil, channeldb.HtlcStateAccepted) {
|
||||
for _, htlc := range inv.HTLCSet(nil, HtlcStateAccepted) {
|
||||
if htlc.MppTotalAmt > 0 {
|
||||
return nil, ctx.failRes(ResultMppInProgress), nil
|
||||
}
|
||||
@@ -418,7 +417,7 @@ func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
}
|
||||
|
||||
// Record HTLC in the invoice database.
|
||||
newHtlcs := map[models.CircuitKey]*channeldb.HtlcAcceptDesc{
|
||||
newHtlcs := map[CircuitKey]*HtlcAcceptDesc{
|
||||
ctx.circuitKey: {
|
||||
Amt: ctx.amtPaid,
|
||||
Expiry: ctx.expiry,
|
||||
@@ -427,17 +426,17 @@ func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
},
|
||||
}
|
||||
|
||||
update := channeldb.InvoiceUpdateDesc{
|
||||
update := InvoiceUpdateDesc{
|
||||
AddHtlcs: newHtlcs,
|
||||
}
|
||||
|
||||
// Don't update invoice state if we are accepting a duplicate payment.
|
||||
// We do accept or settle the HTLC.
|
||||
switch inv.State {
|
||||
case channeldb.ContractAccepted:
|
||||
case ContractAccepted:
|
||||
return &update, ctx.acceptRes(resultDuplicateToAccepted), nil
|
||||
|
||||
case channeldb.ContractSettled:
|
||||
case ContractSettled:
|
||||
return &update, ctx.settleRes(
|
||||
*inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
|
||||
), nil
|
||||
@@ -446,15 +445,15 @@ func updateLegacy(ctx *invoiceUpdateCtx,
|
||||
// Check to see if we can settle or this is an hold invoice and we need
|
||||
// to wait for the preimage.
|
||||
if inv.HodlInvoice {
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractAccepted,
|
||||
update.State = &InvoiceStateUpdateDesc{
|
||||
NewState: ContractAccepted,
|
||||
}
|
||||
|
||||
return &update, ctx.acceptRes(resultAccepted), nil
|
||||
}
|
||||
|
||||
update.State = &channeldb.InvoiceStateUpdateDesc{
|
||||
NewState: channeldb.ContractSettled,
|
||||
update.State = &InvoiceStateUpdateDesc{
|
||||
NewState: ContractSettled,
|
||||
Preimage: inv.Terms.PaymentPreimage,
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user