mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-07-12 14:12:27 +02:00
contractcourt: persist timed out incoming htlc resolver reports
Incoming htlcs that are timed out or failed (invalid htlc or invoice condition not met), save a single on chain resolution because we don't need to take any actions on them ourselves (we don't need to worry about 2 stage claims since this is the success path for our peer).
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,6 +28,7 @@ var (
|
||||
testResCircuitKey = channeldb.CircuitKey{}
|
||||
testOnionBlob = []byte{4, 5, 6}
|
||||
testAcceptHeight int32 = 1234
|
||||
testHtlcAmount = 2300
|
||||
)
|
||||
|
||||
// TestHtlcIncomingResolverFwdPreimageKnown tests resolution of a forwarded htlc
|
||||
@ -34,11 +37,7 @@ func TestHtlcIncomingResolverFwdPreimageKnown(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
ctx := newIncomingResolverTestContext(t, false)
|
||||
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
||||
ctx.resolve()
|
||||
ctx.waitForResult(true)
|
||||
@ -51,11 +50,7 @@ func TestHtlcIncomingResolverFwdContestedSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
ctx := newIncomingResolverTestContext(t, false)
|
||||
ctx.resolve()
|
||||
|
||||
// Simulate a new block coming in. HTLC is not yet expired.
|
||||
@ -71,16 +66,36 @@ func TestHtlcIncomingResolverFwdContestedTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
ctx := newIncomingResolverTestContext(t, false)
|
||||
|
||||
// Replace our checkpoint with one which will push reports into a
|
||||
// channel for us to consume. We replace this function on the resolver
|
||||
// itself because it is created by the test context.
|
||||
reportChan := make(chan *channeldb.ResolverReport)
|
||||
ctx.resolver.Checkpoint = func(_ ContractResolver,
|
||||
reports ...*channeldb.ResolverReport) error {
|
||||
|
||||
// Send all of our reports into the channel.
|
||||
for _, report := range reports {
|
||||
reportChan <- report
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.resolve()
|
||||
|
||||
// Simulate a new block coming in. HTLC expires.
|
||||
ctx.notifyEpoch(testHtlcExpiry)
|
||||
|
||||
// Assert that we have a failure resolution because our invoice was
|
||||
// cancelled.
|
||||
assertResolverReport(t, reportChan, &channeldb.ResolverReport{
|
||||
Amount: lnwire.MilliSatoshi(testHtlcAmount).ToSatoshis(),
|
||||
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
||||
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
|
||||
})
|
||||
|
||||
ctx.waitForResult(false)
|
||||
}
|
||||
|
||||
@ -90,11 +105,7 @@ func TestHtlcIncomingResolverFwdTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testHtlcExpiry,
|
||||
invoices.ResultInvoiceNotFound,
|
||||
)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
ctx.witnessBeacon.lookupPreimage[testResHash] = testResPreimage
|
||||
ctx.resolver.htlcExpiry = 90
|
||||
ctx.resolve()
|
||||
@ -107,7 +118,7 @@ func TestHtlcIncomingResolverExitSettle(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
ctx.registry.notifyResolution = invoices.NewSettleResolution(
|
||||
testResPreimage, testResCircuitKey, testAcceptHeight,
|
||||
invoices.ResultReplayToSettled,
|
||||
@ -138,7 +149,7 @@ func TestHtlcIncomingResolverExitCancel(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
ctx.registry.notifyResolution = invoices.NewFailResolution(
|
||||
testResCircuitKey, testAcceptHeight,
|
||||
invoices.ResultInvoiceAlreadyCanceled,
|
||||
@ -154,7 +165,7 @@ func TestHtlcIncomingResolverExitSettleHodl(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
ctx.resolve()
|
||||
|
||||
notifyData := <-ctx.registry.notifyChan
|
||||
@ -172,9 +183,34 @@ func TestHtlcIncomingResolverExitTimeoutHodl(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
|
||||
// Replace our checkpoint with one which will push reports into a
|
||||
// channel for us to consume. We replace this function on the resolver
|
||||
// itself because it is created by the test context.
|
||||
reportChan := make(chan *channeldb.ResolverReport)
|
||||
ctx.resolver.Checkpoint = func(_ ContractResolver,
|
||||
reports ...*channeldb.ResolverReport) error {
|
||||
|
||||
// Send all of our reports into the channel.
|
||||
for _, report := range reports {
|
||||
reportChan <- report
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.resolve()
|
||||
ctx.notifyEpoch(testHtlcExpiry)
|
||||
|
||||
// Assert that we have a failure resolution because our invoice was
|
||||
// cancelled.
|
||||
assertResolverReport(t, reportChan, &channeldb.ResolverReport{
|
||||
Amount: lnwire.MilliSatoshi(testHtlcAmount).ToSatoshis(),
|
||||
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
||||
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
|
||||
})
|
||||
|
||||
ctx.waitForResult(false)
|
||||
}
|
||||
|
||||
@ -184,25 +220,62 @@ func TestHtlcIncomingResolverExitCancelHodl(t *testing.T) {
|
||||
t.Parallel()
|
||||
defer timeout(t)()
|
||||
|
||||
ctx := newIncomingResolverTestContext(t)
|
||||
ctx := newIncomingResolverTestContext(t, true)
|
||||
|
||||
// Replace our checkpoint with one which will push reports into a
|
||||
// channel for us to consume. We replace this function on the resolver
|
||||
// itself because it is created by the test context.
|
||||
reportChan := make(chan *channeldb.ResolverReport)
|
||||
ctx.resolver.Checkpoint = func(_ ContractResolver,
|
||||
reports ...*channeldb.ResolverReport) error {
|
||||
|
||||
// Send all of our reports into the channel.
|
||||
for _, report := range reports {
|
||||
reportChan <- report
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.resolve()
|
||||
notifyData := <-ctx.registry.notifyChan
|
||||
notifyData.hodlChan <- invoices.NewFailResolution(
|
||||
testResCircuitKey, testAcceptHeight, invoices.ResultCanceled,
|
||||
)
|
||||
|
||||
// Assert that we have a failure resolution because our invoice was
|
||||
// cancelled.
|
||||
assertResolverReport(t, reportChan, &channeldb.ResolverReport{
|
||||
Amount: lnwire.MilliSatoshi(testHtlcAmount).ToSatoshis(),
|
||||
ResolverType: channeldb.ResolverTypeIncomingHtlc,
|
||||
ResolverOutcome: channeldb.ResolverOutcomeAbandoned,
|
||||
})
|
||||
|
||||
ctx.waitForResult(false)
|
||||
}
|
||||
|
||||
type mockHopIterator struct {
|
||||
isExit bool
|
||||
hop.Iterator
|
||||
}
|
||||
|
||||
func (h *mockHopIterator) HopPayload() (*hop.Payload, error) {
|
||||
return nil, nil
|
||||
var nextAddress [8]byte
|
||||
if !h.isExit {
|
||||
nextAddress = [8]byte{0x01}
|
||||
}
|
||||
|
||||
return hop.NewLegacyPayload(&sphinx.HopData{
|
||||
Realm: [1]byte{},
|
||||
NextAddress: nextAddress,
|
||||
ForwardAmount: 100,
|
||||
OutgoingCltv: 40,
|
||||
ExtraBytes: [12]byte{},
|
||||
}), nil
|
||||
}
|
||||
|
||||
type mockOnionProcessor struct {
|
||||
isExit bool
|
||||
offeredOnionBlob []byte
|
||||
}
|
||||
|
||||
@ -215,7 +288,7 @@ func (o *mockOnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte) (
|
||||
}
|
||||
o.offeredOnionBlob = data
|
||||
|
||||
return &mockHopIterator{}, nil
|
||||
return &mockHopIterator{isExit: o.isExit}, nil
|
||||
}
|
||||
|
||||
type incomingResolverTestContext struct {
|
||||
@ -229,7 +302,7 @@ type incomingResolverTestContext struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
||||
func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolverTestContext {
|
||||
notifier := &mockNotifier{
|
||||
epochChan: make(chan *chainntnfs.BlockEpoch),
|
||||
spendChan: make(chan *chainntnfs.SpendDetail),
|
||||
@ -240,7 +313,7 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
||||
notifyChan: make(chan notifyExitHopData, 1),
|
||||
}
|
||||
|
||||
onionProcessor := &mockOnionProcessor{}
|
||||
onionProcessor := &mockOnionProcessor{isExit: isExit}
|
||||
|
||||
checkPointChan := make(chan struct{}, 1)
|
||||
|
||||
@ -272,6 +345,7 @@ func newIncomingResolverTestContext(t *testing.T) *incomingResolverTestContext {
|
||||
contractResolverKit: *newContractResolverKit(cfg),
|
||||
htlcResolution: lnwallet.IncomingHtlcResolution{},
|
||||
htlc: channeldb.HTLC{
|
||||
Amt: lnwire.MilliSatoshi(testHtlcAmount),
|
||||
RHash: testResHash,
|
||||
OnionBlob: testOnionBlob,
|
||||
},
|
||||
|
Reference in New Issue
Block a user