contractcourt: add tests for mempool rejection.
Add a test where the channel arbitrator starts up correctly when a prior unilateral close of a channel did not broadcast for specific reasons. Also add a test which ensures that when a crib output is rejected by the bitcoin backend the startup works correctly for specific errors.
This commit is contained in:
2 files changed
+189
-2
No files matched your search
@@ -2686,6 +2686,91 @@ func TestChannelArbitratorAnchors(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestChannelArbitratorStartAfterCommitmentRejected tests that when we run into
|
||||||
|
// the case where our commitment tx is rejected by our bitcoin backend we still
|
||||||
|
// continue to startup the arbitrator for a specific set of errors.
|
||||||
|
func TestChannelArbitratorStartAfterCommitmentRejected(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// The specific error during broadcasting the transaction.
|
||||||
|
broadcastErr error
|
||||||
|
|
||||||
|
// expected state when the startup of the arbitrator succeeds.
|
||||||
|
expectedState ArbitratorState
|
||||||
|
|
||||||
|
expectedStartup bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Commitment is rejected because of low mempool " +
|
||||||
|
"fees",
|
||||||
|
broadcastErr: lnwallet.ErrMempoolFee,
|
||||||
|
expectedState: StateCommitmentBroadcasted,
|
||||||
|
expectedStartup: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We map a rejected rbf transaction to ErrDoubleSpend
|
||||||
|
// in lnd.
|
||||||
|
name: "Commitment is rejected because of a " +
|
||||||
|
"rbf transaction not succeeding",
|
||||||
|
broadcastErr: lnwallet.ErrDoubleSpend,
|
||||||
|
expectedState: StateCommitmentBroadcasted,
|
||||||
|
expectedStartup: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Commitment is rejected with an " +
|
||||||
|
"unmatched error",
|
||||||
|
broadcastErr: fmt.Errorf("Reject Commitment Tx"),
|
||||||
|
expectedState: StateBroadcastCommit,
|
||||||
|
expectedStartup: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// We'll create the arbitrator and its backing log
|
||||||
|
// to signal that it's already in the process of being
|
||||||
|
// force closed.
|
||||||
|
log := &mockArbitratorLog{
|
||||||
|
newStates: make(chan ArbitratorState, 5),
|
||||||
|
state: StateBroadcastCommit,
|
||||||
|
}
|
||||||
|
chanArbCtx, err := createTestChannelArbitrator(t, log)
|
||||||
|
require.NoError(t, err, "unable to create "+
|
||||||
|
"ChannelArbitrator")
|
||||||
|
|
||||||
|
chanArb := chanArbCtx.chanArb
|
||||||
|
|
||||||
|
// Customize the PublishTx function of the arbitrator.
|
||||||
|
chanArb.cfg.PublishTx = func(*wire.MsgTx,
|
||||||
|
string) error {
|
||||||
|
|
||||||
|
return test.broadcastErr
|
||||||
|
}
|
||||||
|
err = chanArb.Start(nil)
|
||||||
|
if !test.expectedStartup {
|
||||||
|
require.ErrorIs(t, err, test.broadcastErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, chanArb.Stop())
|
||||||
|
})
|
||||||
|
|
||||||
|
// In case the startup succeeds we check that the state
|
||||||
|
// is as expected.
|
||||||
|
chanArbCtx.AssertStateTransitions(test.expectedState)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// putResolverReportInChannel returns a put report function which will pipe
|
// putResolverReportInChannel returns a put report function which will pipe
|
||||||
// reports into the channel provided.
|
// reports into the channel provided.
|
||||||
func putResolverReportInChannel(reports chan *channeldb.ResolverReport) func(
|
func putResolverReportInChannel(reports chan *channeldb.ResolverReport) func(
|
||||||
|
|||||||
@@ -504,8 +504,7 @@ func createNurseryTestContext(t *testing.T,
|
|||||||
/// Restart nursery.
|
/// Restart nursery.
|
||||||
nurseryCfg.SweepInput = ctx.sweeper.sweepInput
|
nurseryCfg.SweepInput = ctx.sweeper.sweepInput
|
||||||
ctx.nursery = NewUtxoNursery(&nurseryCfg)
|
ctx.nursery = NewUtxoNursery(&nurseryCfg)
|
||||||
ctx.nursery.Start()
|
require.NoError(t, ctx.nursery.Start())
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,6 +645,109 @@ func incubateTestOutput(t *testing.T, nursery *UtxoNursery,
|
|||||||
return outgoingRes
|
return outgoingRes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRejectedCribTransaction makes sure that our nursery does not fail to
|
||||||
|
// start up in case a Crib transaction (htlc-timeout) is rejected by the
|
||||||
|
// bitcoin backend for some excepted reasons.
|
||||||
|
func TestRejectedCribTransaction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
// The specific error during broadcasting the transaction.
|
||||||
|
broadcastErr error
|
||||||
|
|
||||||
|
// expectErr specifies whether the rejection of the transaction
|
||||||
|
// fails the nursery engine.
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Crib tx is rejected because of low mempool " +
|
||||||
|
"fees",
|
||||||
|
broadcastErr: lnwallet.ErrMempoolFee,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We map a rejected rbf transaction to ErrDoubleSpend
|
||||||
|
// in lnd.
|
||||||
|
name: "Crib tx is rejected because of a " +
|
||||||
|
"rbf transaction not succeeding",
|
||||||
|
broadcastErr: lnwallet.ErrDoubleSpend,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Crib tx is rejected with an " +
|
||||||
|
"unmatched error",
|
||||||
|
broadcastErr: fmt.Errorf("Reject Commitment Tx"),
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// The checkStartStop function just calls the callback
|
||||||
|
// here to make sure the restart routine works
|
||||||
|
// correctly.
|
||||||
|
ctx := createNurseryTestContext(t,
|
||||||
|
func(callback func()) bool {
|
||||||
|
callback()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
outgoingRes := createOutgoingRes(true)
|
||||||
|
|
||||||
|
ctx.nursery.cfg.PublishTransaction =
|
||||||
|
func(tx *wire.MsgTx, source string) error {
|
||||||
|
log.Tracef("Publishing tx %v "+
|
||||||
|
"by %v", tx.TxHash(), source)
|
||||||
|
return test.broadcastErr
|
||||||
|
}
|
||||||
|
ctx.notifyEpoch(125)
|
||||||
|
|
||||||
|
// Hand off to nursery.
|
||||||
|
err := ctx.nursery.IncubateOutputs(
|
||||||
|
testChanPoint,
|
||||||
|
[]lnwallet.OutgoingHtlcResolution{*outgoingRes},
|
||||||
|
nil, 0,
|
||||||
|
)
|
||||||
|
if test.expectErr {
|
||||||
|
require.ErrorIs(t, err, test.broadcastErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure that a restart is not affected by the
|
||||||
|
// rejected Crib transaction.
|
||||||
|
ctx.restart()
|
||||||
|
|
||||||
|
// Confirm the timeout tx. This should promote the
|
||||||
|
// HTLC to KNDR state.
|
||||||
|
timeoutTxHash := outgoingRes.SignedTimeoutTx.TxHash()
|
||||||
|
err = ctx.notifier.ConfirmTx(&timeoutTxHash, 126)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Wait for output to be promoted in store to KNDR.
|
||||||
|
select {
|
||||||
|
case <-ctx.store.cribToKinderChan:
|
||||||
|
case <-time.After(defaultTestTimeout):
|
||||||
|
t.Fatalf("output not promoted to KNDR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify arrival of block where second level HTLC
|
||||||
|
// unlocks.
|
||||||
|
ctx.notifyEpoch(128)
|
||||||
|
|
||||||
|
// Check final sweep into wallet.
|
||||||
|
testSweepHtlc(t, ctx)
|
||||||
|
|
||||||
|
// Cleanup utxonursery.
|
||||||
|
ctx.finish()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertNurseryReport(t *testing.T, nursery *UtxoNursery,
|
func assertNurseryReport(t *testing.T, nursery *UtxoNursery,
|
||||||
expectedNofHtlcs int, expectedStage uint32,
|
expectedNofHtlcs int, expectedStage uint32,
|
||||||
expectedLimboBalance btcutil.Amount) {
|
expectedLimboBalance btcutil.Amount) {
|
||||||
|
|||||||
Reference in new issue
Block a user