Skip to content

itest: add breach itest for custom channels #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ require (
github.com/lightninglabs/loop/swapserverrpc v1.0.8
github.com/lightninglabs/pool v0.6.5-beta.0.20240604070222-e121aadb3289
github.com/lightninglabs/pool/auctioneerrpc v1.1.2
github.com/lightninglabs/taproot-assets v0.3.3-0.20240621202612-eae23c4f77e8
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240621222000-c6e4d621d2b0
github.com/lightninglabs/taproot-assets v0.3.3-0.20240625161215-838206d62c99
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240625154246-4e968d9b520c
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/fn v1.1.0
github.com/lightningnetwork/lnd/kvdb v1.4.8
Expand Down Expand Up @@ -227,3 +227,5 @@ replace github.com/lightninglabs/lightning-terminal/autopilotserverrpc => ./auto
replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display

go 1.22.3

toolchain go1.22.4
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1172,12 +1172,12 @@ github.com/lightninglabs/pool/auctioneerrpc v1.1.2 h1:Dbg+9Z9jXnhimR27EN37foc4aB
github.com/lightninglabs/pool/auctioneerrpc v1.1.2/go.mod h1:1wKDzN2zEP8srOi0B9iySlEsPdoPhw6oo3Vbm1v4Mhw=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
github.com/lightninglabs/taproot-assets v0.3.3-0.20240621202612-eae23c4f77e8 h1:bjdVqtwUirxbi8mkq86oo4Q9WqFnfadzwnxTXNhAtoQ=
github.com/lightninglabs/taproot-assets v0.3.3-0.20240621202612-eae23c4f77e8/go.mod h1:FAmcLipYgjm2jpqI2fiSOax8Oah/pekkGLfhJc6dwD0=
github.com/lightninglabs/taproot-assets v0.3.3-0.20240625161215-838206d62c99 h1:eMWI/Ob3Gv+7dHs7b9WA9Rpsvc32w4jPj+iKQ6+lD4s=
github.com/lightninglabs/taproot-assets v0.3.3-0.20240625161215-838206d62c99/go.mod h1:KhiaNUkgI3zIYNzfUoEClJjInXt5vScmnLVIvuUzWXY=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240621222000-c6e4d621d2b0 h1:v72KQn3kiNmPICIYsZSRnBdnDZpOQ3CUNc20E7v3Z3M=
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240621222000-c6e4d621d2b0/go.mod h1:L3IArArdRrWtuw+wNsUlibuGmf/08Odsm/zo3+bPXuM=
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240625154246-4e968d9b520c h1:10hVKzgsnpuzOOgkYAhThUtDiq3fBBJBeZWdir+0ptk=
github.com/lightningnetwork/lnd v0.18.0-beta.rc3.0.20240625154246-4e968d9b520c/go.mod h1:L3IArArdRrWtuw+wNsUlibuGmf/08Odsm/zo3+bPXuM=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
Expand Down
229 changes: 225 additions & 4 deletions itest/litd_custom_channels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,227 @@ func testCustomChannelsForceClose(_ context.Context, net *NetworkHarness,
t.Logf("Dave UTXOs: %v", toProtoJSON(t.t, daveUTXOs))
}

func testCustomChannelsBreach(_ context.Context, net *NetworkHarness,
t *harnessTest) {

lndArgs := slices.Clone(lndArgsTemplate)
litdArgs := slices.Clone(litdArgsTemplate)

// Zane will act as our Universe server for the duration of the test.
zane, err := net.NewNode(
t.t, "Zane", lndArgs, false, true, litdArgs...,
)
require.NoError(t.t, err)

// For our litd args, make sure that they all seen Zane as the main
// Universe server.
litdArgs = append(litdArgs, fmt.Sprintf(
"--taproot-assets.proofcourieraddr=%s://%s",
proof.UniverseRpcCourierType, zane.Cfg.LitAddr(),
))

// Charlie will be the breached party. We set --nolisten to ensure Dave
// won't be able to connect to him and trigger the channel protection
// logic automatically. We also can't have Charlie automatically
// reconnect too early, otherwise DLP would be initiated instead of the
// breach we want to provoke.
charlieFlags := append(
slices.Clone(lndArgs), "--nolisten", "--minbackoff=1h",
)

// For this simple test, we'll just have Carol -> Dave as an assets
// channel.
charlie, err := net.NewNode(
t.t, "Charlie", charlieFlags, false, true, litdArgs...,
)
require.NoError(t.t, err)

dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...)
require.NoError(t.t, err)

// Next we'll connect all the nodes and also fund them with some coins.
nodes := []*HarnessNode{charlie, dave}
connectAllNodes(t.t, net, nodes)
fundAllNodes(t.t, net, nodes)

charlieTap := newTapClient(t.t, charlie)
daveTap := newTapClient(t.t, dave)

ctxb := context.Background()

// Now we'll make an asset for Charlie that we'll use in the test to
// open a channel.
mintedAssets := itest.MintAssetsConfirmBatch(
t.t, t.lndHarness.Miner.Client, charlieTap,
[]*mintrpc.MintAssetRequest{
{
Asset: itestAsset,
},
},
)
cents := mintedAssets[0]
assetID := cents.AssetGenesis.AssetId

t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount)
syncUniverses(t.t, charlieTap, dave)
t.Logf("Universes synced between all nodes, distributing assets...")

// TODO(roasbeef): consolidate w/ the other test

// Next we can open an asset channel from Charlie -> Dave, then kick
// off the main scenario.
t.Logf("Opening asset channels...")
assetFundResp, err := charlieTap.FundChannel(
ctxb, &tchrpc.FundChannelRequest{
AssetAmount: fundingAmount,
AssetId: assetID,
PeerPubkey: dave.PubKey[:],
FeeRateSatPerVbyte: 5,
},
)
require.NoError(t.t, err)
t.Logf("Funded channel between Charlie and Dave: %v", assetFundResp)

// With the channel open, mine a block to confirm it.
mineBlocks(t, net, 6, 1)

time.Sleep(time.Second * 1)

// Next, we'll make keysend payments from Charlie to Dave. we'll use
// this to reach a state where both parties have funds in the channel.
const (
numPayments = 5
keySendAmount = 100
btcAmt = int64(5_000)
)
for i := 0; i < numPayments; i++ {
sendAssetKeySendPayment(
t.t, charlie, dave, keySendAmount, assetID,
fn.Some(btcAmt),
)
}

logBalance(t.t, nodes, assetID, "after keysend -- breach state")

// Now we'll create an on disk snapshot that we'll use to restore back
// to as our breached state.
require.NoError(t.t, net.StopAndBackupDB(dave))
connectAllNodes(t.t, net, nodes)

// We'll send one more keysend payment now to revoke the state we were
// just at above.
sendAssetKeySendPayment(
t.t, charlie, dave, keySendAmount, assetID, fn.Some(btcAmt),
)
logBalance(t.t, nodes, assetID, "after keysend -- final state")

// With the final state achieved, we'll now restore Dave (who will be
// force closing) to that old state, the breach state.
require.NoError(t.t, net.StopAndRestoreDB(dave))

// With Dave restored, we'll now execute the force close.
t.Logf("Force close by Dave to breach...")
daveChanPoint := &lnrpc.ChannelPoint{
OutputIndex: uint32(assetFundResp.OutputIndex),
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
FundingTxidStr: assetFundResp.Txid,
},
}
_, breachTxid, err := net.CloseChannel(dave, daveChanPoint, true)
require.NoError(t.t, err)

t.Logf("Channel closed! Mining blocks, close_txid=%v", breachTxid)

// Next, we'll mine a block to confirm the breach transaction.
mineBlocks(t, net, 1, 1)

// We should be able to find the transfer of the breach for both
// parties.
charlieBreachTransfer := locateAssetTransfers(
t.t, charlieTap, *breachTxid,
)
daveBreachTransfer := locateAssetTransfers(
t.t, daveTap, *breachTxid,
)

t.Logf("Charlie breach transfer: %v",
toProtoJSON(t.t, charlieBreachTransfer))
t.Logf("Dave breach transfer: %v",
toProtoJSON(t.t, daveBreachTransfer))

// With the breach transaction mined, Charlie should now have a
// transaction in the mempool sweeping the *both* commitment outputs.
charlieJusticeTxid, err := waitForNTxsInMempool(
net.Miner.Client, 1, time.Second*5,
)
require.NoError(t.t, err)

t.Logf("Charlie justice txid: %v", charlieJusticeTxid)

// Next, we'll mine a block to confirm Charlie's justice transaction.
mineBlocks(t, net, 1, 1)

// Charlie should now have a transfer for his justice transaction.
charlieJusticeTransfer := locateAssetTransfers(
t.t, charlieTap, *charlieJusticeTxid[0],
)

t.Logf("Charlie justice transfer: %v",
toProtoJSON(t.t, charlieJusticeTransfer))

// Charlie's balance should now be the same as before the breach
// attempt: the amount he minted at the very start.
charlieBalance := itestAsset.Amount
assertAssetBalance(t.t, charlieTap, assetID, charlieBalance)

t.Logf("Charlie balance after breach: %d", charlieBalance)

// Charlie should now have 2 total UTXOs: the change from the funding
// output, and now the sweep output from the justice transaction.
charlieUTXOs := assertNumAssetUTXOs(t.t, charlieTap, 2)

t.Logf("Charlie UTXOs after breach: %v", toProtoJSON(t.t, charlieUTXOs))
}

func assertNumAssetUTXOs(t *testing.T, tapdClient *tapClient,
numUTXOs int) *taprpc.ListUtxosResponse {

ctxb := context.Background()

err := wait.NoError(func() error {
clientUTXOs, err := tapdClient.ListUtxos(
ctxb, &taprpc.ListUtxosRequest{},
)
if err != nil {
return err
}

if len(clientUTXOs.ManagedUtxos) != numUTXOs {
return fmt.Errorf("expected %v UTXO, got %d", numUTXOs,
len(clientUTXOs.ManagedUtxos))
}

return nil
}, defaultTimeout)

clientUTXOs, err2 := tapdClient.ListUtxos(
ctxb, &taprpc.ListUtxosRequest{},
)
require.NoError(t, err2)

if err != nil {
t.Logf("wrong amount of UTXOs, got %d, expected %d: %v",
len(clientUTXOs.ManagedUtxos), numUTXOs,
toProtoJSON(t, clientUTXOs))

t.Fatalf("failed to assert UTXOs: %v", err)

return nil
}

return clientUTXOs
}

func locateAssetTransfers(t *testing.T, tapdClient *tapClient,
txid chainhash.Hash) *taprpc.AssetTransfer {

Expand All @@ -953,12 +1174,12 @@ func locateAssetTransfers(t *testing.T, tapdClient *tapClient,
},
)
if err != nil {
return fmt.Errorf("unable to list charlie "+
"transfers: %w", err)
return fmt.Errorf("unable to list %v transfers: %w",
tapdClient.node.Name(), err)
}
if len(forceCloseTransfer.Transfers) != 1 {
return fmt.Errorf("charlie is missing force close " +
"transfer")
return fmt.Errorf("%v is missing force close "+
"transfer", tapdClient.node.Name())
}

transfer = forceCloseTransfer.Transfers[0]
Expand Down
38 changes: 38 additions & 0 deletions itest/litd_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ type LitNodeConfig struct {

LitPort int
LitRESTPort int

// backupDBDir is the path where a database backup is stored, if any.
backupDBDir string
}

func (cfg *LitNodeConfig) LitAddr() string {
Expand Down Expand Up @@ -2062,3 +2065,38 @@ func connectLitRPC(ctx context.Context, hostPort, tlsCertPath,

return grpc.DialContext(ctx, hostPort, opts...)
}

// copyAll copies all files and directories from srcDir to dstDir recursively.
// Note that this function does not support links.
func copyAll(dstDir, srcDir string) error {
entries, err := os.ReadDir(srcDir)
if err != nil {
return err
}

for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())

info, err := os.Stat(srcPath)
if err != nil {
return err
}

if info.IsDir() {
err := os.Mkdir(dstPath, info.Mode())
if err != nil && !os.IsExist(err) {
return err
}

err = copyAll(dstPath, srcPath)
if err != nil {
return err
}
} else if err := CopyFile(dstPath, srcPath); err != nil {
return err
}
}

return nil
}
4 changes: 4 additions & 0 deletions itest/litd_test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ var allTestCases = []*testCase{
name: "test custom channels force close",
test: testCustomChannelsForceClose,
},
{
name: "test custom channels breach",
test: testCustomChannelsBreach,
},
}
Loading
Loading