You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
5.9 KiB
240 lines
5.9 KiB
package contractcourt |
|
|
|
import ( |
|
"fmt" |
|
"testing" |
|
|
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/lightningnetwork/lnd/chainntnfs" |
|
"github.com/lightningnetwork/lnd/channeldb" |
|
"github.com/lightningnetwork/lnd/input" |
|
"github.com/lightningnetwork/lnd/kvdb" |
|
"github.com/lightningnetwork/lnd/lntest/mock" |
|
"github.com/lightningnetwork/lnd/lntypes" |
|
"github.com/lightningnetwork/lnd/lnwallet" |
|
"github.com/lightningnetwork/lnd/lnwire" |
|
) |
|
|
|
const ( |
|
outgoingContestHtlcExpiry = 110 |
|
) |
|
|
|
// TestHtlcOutgoingResolverTimeout tests resolution of an offered htlc that |
|
// timed out. |
|
func TestHtlcOutgoingResolverTimeout(t *testing.T) { |
|
t.Parallel() |
|
defer timeout(t)() |
|
|
|
// Setup the resolver with our test resolution. |
|
ctx := newOutgoingResolverTestContext(t) |
|
|
|
// Start the resolution process in a goroutine. |
|
ctx.resolve() |
|
|
|
// Notify arrival of the block after which the timeout path of the htlc |
|
// unlocks. |
|
ctx.notifyEpoch(outgoingContestHtlcExpiry - 1) |
|
|
|
// Assert that the resolver finishes without error and transforms in a |
|
// timeout resolver. |
|
ctx.waitForResult(true) |
|
} |
|
|
|
// TestHtlcOutgoingResolverRemoteClaim tests resolution of an offered htlc that |
|
// is claimed by the remote party. |
|
func TestHtlcOutgoingResolverRemoteClaim(t *testing.T) { |
|
t.Parallel() |
|
defer timeout(t)() |
|
|
|
// Setup the resolver with our test resolution and start the resolution |
|
// process. |
|
ctx := newOutgoingResolverTestContext(t) |
|
|
|
// Replace our mocked checkpoint function with one which will push |
|
// reports into a channel for us to consume. We do so on the resolver |
|
// level because our test context has already created the resolver. |
|
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() |
|
|
|
// The remote party sweeps the htlc. Notify our resolver of this event. |
|
preimage := lntypes.Preimage{} |
|
spendTx := &wire.MsgTx{ |
|
TxIn: []*wire.TxIn{ |
|
{ |
|
Witness: [][]byte{ |
|
{0}, {1}, {2}, preimage[:], |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
spendHash := spendTx.TxHash() |
|
|
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{ |
|
SpendingTx: spendTx, |
|
SpenderTxHash: &spendHash, |
|
} |
|
|
|
// We expect the extracted preimage to be added to the witness beacon. |
|
<-ctx.preimageDB.newPreimages |
|
|
|
// We also expect a resolution message to the incoming side of the |
|
// circuit. |
|
<-ctx.resolutionChan |
|
|
|
// Finally, check that we have a report as expected. |
|
expectedReport := &channeldb.ResolverReport{ |
|
OutPoint: wire.OutPoint{}, |
|
Amount: 0, |
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc, |
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed, |
|
SpendTxID: &spendHash, |
|
} |
|
|
|
assertResolverReport(t, reportChan, expectedReport) |
|
|
|
// Assert that the resolver finishes without error. |
|
ctx.waitForResult(false) |
|
} |
|
|
|
type resolveResult struct { |
|
err error |
|
nextResolver ContractResolver |
|
} |
|
|
|
type outgoingResolverTestContext struct { |
|
resolver *htlcOutgoingContestResolver |
|
notifier *mock.ChainNotifier |
|
preimageDB *mockWitnessBeacon |
|
resolverResultChan chan resolveResult |
|
resolutionChan chan ResolutionMsg |
|
t *testing.T |
|
} |
|
|
|
func newOutgoingResolverTestContext(t *testing.T) *outgoingResolverTestContext { |
|
notifier := &mock.ChainNotifier{ |
|
EpochChan: make(chan *chainntnfs.BlockEpoch), |
|
SpendChan: make(chan *chainntnfs.SpendDetail), |
|
ConfChan: make(chan *chainntnfs.TxConfirmation), |
|
} |
|
|
|
checkPointChan := make(chan struct{}, 1) |
|
resolutionChan := make(chan ResolutionMsg, 1) |
|
|
|
preimageDB := newMockWitnessBeacon() |
|
|
|
onionProcessor := &mockOnionProcessor{} |
|
|
|
chainCfg := ChannelArbitratorConfig{ |
|
ChainArbitratorConfig: ChainArbitratorConfig{ |
|
Notifier: notifier, |
|
PreimageDB: preimageDB, |
|
DeliverResolutionMsg: func(msgs ...ResolutionMsg) error { |
|
if len(msgs) != 1 { |
|
return fmt.Errorf("expected 1 "+ |
|
"resolution msg, instead got %v", |
|
len(msgs)) |
|
} |
|
|
|
resolutionChan <- msgs[0] |
|
return nil |
|
}, |
|
OnionProcessor: onionProcessor, |
|
}, |
|
PutResolverReport: func(_ kvdb.RwTx, |
|
_ *channeldb.ResolverReport) error { |
|
|
|
return nil |
|
}, |
|
} |
|
|
|
outgoingRes := lnwallet.OutgoingHtlcResolution{ |
|
Expiry: outgoingContestHtlcExpiry, |
|
SweepSignDesc: input.SignDescriptor{ |
|
Output: &wire.TxOut{}, |
|
}, |
|
} |
|
|
|
cfg := ResolverConfig{ |
|
ChannelArbitratorConfig: chainCfg, |
|
Checkpoint: func(_ ContractResolver, |
|
_ ...*channeldb.ResolverReport) error { |
|
|
|
checkPointChan <- struct{}{} |
|
return nil |
|
}, |
|
} |
|
|
|
resolver := &htlcOutgoingContestResolver{ |
|
htlcTimeoutResolver: &htlcTimeoutResolver{ |
|
contractResolverKit: *newContractResolverKit(cfg), |
|
htlcResolution: outgoingRes, |
|
htlc: channeldb.HTLC{ |
|
Amt: lnwire.MilliSatoshi(testHtlcAmount), |
|
RHash: testResHash, |
|
OnionBlob: testOnionBlob, |
|
}, |
|
}, |
|
} |
|
|
|
return &outgoingResolverTestContext{ |
|
resolver: resolver, |
|
notifier: notifier, |
|
preimageDB: preimageDB, |
|
resolutionChan: resolutionChan, |
|
t: t, |
|
} |
|
} |
|
|
|
func (i *outgoingResolverTestContext) resolve() { |
|
// Start resolver. |
|
i.resolverResultChan = make(chan resolveResult, 1) |
|
go func() { |
|
nextResolver, err := i.resolver.Resolve() |
|
i.resolverResultChan <- resolveResult{ |
|
nextResolver: nextResolver, |
|
err: err, |
|
} |
|
}() |
|
|
|
// Notify initial block height. |
|
i.notifyEpoch(testInitialBlockHeight) |
|
} |
|
|
|
func (i *outgoingResolverTestContext) notifyEpoch(height int32) { |
|
i.notifier.EpochChan <- &chainntnfs.BlockEpoch{ |
|
Height: height, |
|
} |
|
} |
|
|
|
func (i *outgoingResolverTestContext) waitForResult(expectTimeoutRes bool) { |
|
i.t.Helper() |
|
|
|
result := <-i.resolverResultChan |
|
if result.err != nil { |
|
i.t.Fatal(result.err) |
|
} |
|
|
|
if !expectTimeoutRes { |
|
if result.nextResolver != nil { |
|
i.t.Fatal("expected no next resolver") |
|
} |
|
return |
|
} |
|
|
|
_, ok := result.nextResolver.(*htlcTimeoutResolver) |
|
if !ok { |
|
i.t.Fatal("expected htlcTimeoutResolver") |
|
} |
|
}
|
|
|