invoices: replay awareness
Previously the invoice registry wasn't aware of replayed htlcs. This was dealt with by keeping the invoice accept/settle logic idempotent, so that a replay wouldn't have an effect. This mechanism has two limitations: 1. No accurate tracking of the total amount paid to an invoice. The total amount couldn't just be increased with every htlc received, because it could be a replay which would lead to counting the htlc amount multiple times. Therefore the total amount was set to the amount of the first htlc that was received, even though there may have been multiple htlcs paying to the invoice. 2. Impossible to check htlc expiry consistently for hodl invoices. When an htlc is new, its expiry needs to be checked against the invoice cltv delta. But for a replay, that check must be skipped. The htlc was accepted in time, the invoice was moved to the accepted state and a replay some blocks later shouldn't lead to that htlc being cancelled. Because the invoice registry couldn't recognize replays, it stopped checking htlc expiry heights when the invoice reached the accepted state. This prevents hold htlcs from being cancelled after a restart. But unfortunately this also caused additional htlcs to be accepted on an already accepted invoice without their expiry being checked. In this commit, the invoice registry starts to persistently track htlcs so that replays can be recognized. For replays, an htlc resolution action is returned early. This fixes both limitations mentioned above.
This commit is contained in:
5 files changed
+188
-104
No files matched your search
+16
-25
@@ -10,26 +10,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
testCircuitKey = CircuitKey{
|
||||
ChanID: lnwire.ShortChannelID{
|
||||
BlockHeight: 1, TxIndex: 2, TxPosition: 3,
|
||||
},
|
||||
HtlcID: 4,
|
||||
}
|
||||
|
||||
testHtlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
testCircuitKey: {
|
||||
State: HtlcStateCancelled,
|
||||
AcceptTime: time.Unix(1, 0),
|
||||
AcceptHeight: 100,
|
||||
ResolveTime: time.Unix(2, 0),
|
||||
Amt: 5200,
|
||||
Expiry: 150,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
|
||||
var pre [32]byte
|
||||
if _, err := rand.Read(pre[:]); err != nil {
|
||||
@@ -44,9 +24,8 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
|
||||
PaymentPreimage: pre,
|
||||
Value: value,
|
||||
},
|
||||
Htlcs: testHtlcs,
|
||||
FinalCltvDelta: 50,
|
||||
Expiry: 4000,
|
||||
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||
Expiry: 4000,
|
||||
}
|
||||
i.Memo = []byte("memo")
|
||||
i.Receipt = []byte("receipt")
|
||||
@@ -82,7 +61,7 @@ func TestInvoiceWorkflow(t *testing.T) {
|
||||
// Use single second precision to avoid false positive test
|
||||
// failures due to the monotonic time component.
|
||||
CreationDate: time.Unix(time.Now().Unix(), 0),
|
||||
Htlcs: testHtlcs,
|
||||
Htlcs: map[CircuitKey]*InvoiceHTLC{},
|
||||
}
|
||||
fakeInvoice.Memo = []byte("memo")
|
||||
fakeInvoice.Receipt = []byte("receipt")
|
||||
@@ -383,6 +362,14 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
||||
invoice.Terms.State = ContractSettled
|
||||
invoice.AmtPaid = amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
{}: {
|
||||
Amt: amt,
|
||||
AcceptTime: time.Unix(1, 0),
|
||||
ResolveTime: time.Unix(1, 0),
|
||||
State: HtlcStateSettled,
|
||||
},
|
||||
}
|
||||
|
||||
// We should get back the exact same invoice that we just inserted.
|
||||
if !reflect.DeepEqual(dbInvoice, invoice) {
|
||||
@@ -695,7 +682,11 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) InvoiceUpdateCallback {
|
||||
update := &InvoiceUpdateDesc{
|
||||
Preimage: invoice.Terms.PaymentPreimage,
|
||||
State: ContractSettled,
|
||||
AmtPaid: amt,
|
||||
Htlcs: map[CircuitKey]*HtlcAcceptDesc{
|
||||
{}: {
|
||||
Amt: amt,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return update, nil
|
||||
|
||||
Reference in new issue
Block a user