Skip to content

Commit

Permalink
itest: add new testCustomChannelsHtlcForceClose itest
Browse files Browse the repository at this point in the history
In this commit, we add a new itest that tests the 4 sweeping cases for
HTLCs:
  * local success
  * remote success
  * local timeout
  * remote timeout

To test this, we have Alice load up her commitment transaction with 4
HTLCs (2 incoming, 2 outgoing) using hodl invoices. Then we force close
her commitment transaction. In this scenario, she needs to broadcast a
second level transaction to ultimately timeout, while Bob can sweep
directly from the commitment transaction.
  • Loading branch information
Roasbeef committed Nov 21, 2024
1 parent 252b557 commit a374019
Show file tree
Hide file tree
Showing 5 changed files with 549 additions and 1 deletion.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/urfave/cli v1.22.9
go.etcd.io/bbolt v1.3.11
golang.org/x/crypto v0.25.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
golang.org/x/net v0.27.0
golang.org/x/sync v0.8.0
google.golang.org/grpc v1.65.0
Expand Down Expand Up @@ -201,7 +202,6 @@ require (
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
Expand Down
26 changes: 26 additions & 0 deletions itest/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package itest
import (
"context"
"fmt"
"testing"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -205,3 +207,27 @@ func assertChannelClosed(ctx context.Context, t *harnessTest,

return closingTxid
}

func assertSweepExists(t *testing.T, node *HarnessNode,
witnessType walletrpc.WitnessType) {

ctxb := context.Background()
err := wait.NoError(func() error {
pendingSweeps, err := node.WalletKitClient.PendingSweeps(
ctxb, &walletrpc.PendingSweepsRequest{},
)
if err != nil {
return err
}

for _, sweep := range pendingSweeps.PendingSweeps {
if sweep.WitnessType == witnessType {
return nil
}
}

return fmt.Errorf("failed to find second level sweep: %v",
toProtoJSON(t, pendingSweeps))
}, defaultTimeout)
require.NoError(t, err)
}
88 changes: 88 additions & 0 deletions itest/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1868,3 +1868,91 @@ func toProtoJSON(t *testing.T, resp proto.Message) string {

return string(jsonBytes)
}

func assertNumHtlcs(t *testing.T, node *HarnessNode, expected int) {
t.Helper()

ctxb := context.Background()

err := wait.NoError(func() error {
listChansRequest := &lnrpc.ListChannelsRequest{}
listChansResp, err := node.ListChannels(ctxb, listChansRequest)
if err != nil {
return err
}

var numHtlcs int
for _, channel := range listChansResp.Channels {
numHtlcs += len(channel.PendingHtlcs)
}

if numHtlcs != expected {
return fmt.Errorf("expected %v HTLCs, got %v, %v", expected, numHtlcs, spew.Sdump(toProtoJSON(t, listChansResp)))
}

return nil
}, defaultTimeout)
require.NoError(t, err)
}

type forceCloseExpiryInfo struct {
currentHeight uint32
csvDelay uint32

cltvDelays map[lntypes.Hash]uint32

localAssetBalance uint64
remoteAssetBalance uint64

t *testing.T

node *HarnessNode
}

func (f *forceCloseExpiryInfo) blockTillExpiry(hash lntypes.Hash) uint32 {
ctxb := context.Background()
nodeInfo, err := f.node.GetInfo(ctxb, &lnrpc.GetInfoRequest{})
require.NoError(f.t, err)

cltv, ok := f.cltvDelays[hash]
require.True(f.t, ok)

f.t.Logf("current_height=%v, expiry=%v, mining %v blocks",
nodeInfo.BlockHeight, cltv, cltv-nodeInfo.BlockHeight)

return cltv - nodeInfo.BlockHeight
}

func newCloseExpiryInfo(t *testing.T, node *HarnessNode) forceCloseExpiryInfo {
ctxb := context.Background()

listChansRequest := &lnrpc.ListChannelsRequest{}
listChansResp, err := node.ListChannels(ctxb, listChansRequest)
require.NoError(t, err)

mainChan := listChansResp.Channels[0]

nodeInfo, err := node.GetInfo(ctxb, &lnrpc.GetInfoRequest{})
require.NoError(t, err)

cltvs := make(map[lntypes.Hash]uint32)
for _, htlc := range mainChan.PendingHtlcs {
var payHash lntypes.Hash
copy(payHash[:], htlc.HashLock)
cltvs[payHash] = htlc.ExpirationHeight
}

var assetData rfqmsg.JsonAssetChannel
err = json.Unmarshal(mainChan.CustomChannelData, &assetData)
require.NoError(t, err)

return forceCloseExpiryInfo{
csvDelay: mainChan.CsvDelay,
currentHeight: nodeInfo.BlockHeight,
cltvDelays: cltvs,
localAssetBalance: assetData.Assets[0].LocalBalance,
remoteAssetBalance: assetData.Assets[0].RemoteBalance,
t: t,
node: node,
}
}
Loading

0 comments on commit a374019

Please sign in to comment.