9b08ef6d4e
To make the linter happy, make a pointer to the inner resolver. Otherwise the linter would complain with copylocks: literal copies lock value since we'll add a mutex to the resolver in following commits.
241 lines
5.9 KiB
Go
241 lines
5.9 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"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")
|
|
}
|
|
}
|