mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
2fc8e9d617
We know that onion blobs in lightning are _exactly_ 1366 bytes in lightning, but they are currently expressed as a byte slice in channeldb's HTLC struct. Blobs are currently serialized as var bytes, so we can take advantage of this known length and variable length to add additional data to the inline serialization of our HTLCs, which are otherwise not easily extensible (without creating a new bucket).
242 lines
5.9 KiB
Go
242 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/input"
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
"github.com/lightningnetwork/lnd/lnmock"
|
|
"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()()
|
|
|
|
// 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)
|
|
|
|
// 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()()
|
|
|
|
// 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: lnmock.MockOnion(),
|
|
},
|
|
},
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|