Skip to content

Commit b3442f8

Browse files
committed
itest: add test for zero-value utxo garbage collection
1 parent 930eb9d commit b3442f8

File tree

3 files changed

+202
-2
lines changed

3 files changed

+202
-2
lines changed

itest/full_value_split_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ func testFullValueSend(t *harnessTest) {
6161
1, 2,
6262
)
6363

64-
// Alice should have one more zero-value tombstones in her wallet.
64+
// After the second run, Alice's previous tombstons were swept. She now
65+
// has 1 new tombstone UTXO from the last full-value send.
6566
AssertBalances(
6667
t.t, t.tapd, 0, WithScriptKeyType(asset.ScriptKeyTombstone),
67-
WithNumUtxos(3), WithNumAnchorUtxos(3),
68+
WithNumUtxos(1), WithNumAnchorUtxos(1),
6869
)
6970
AssertBalances(
7071
t.t, secondTapd, mintedAsset.Amount+mintedGroupAsset.Amount,

itest/test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ var allTestCases = []*testCase{
101101
name: "min relay fee bump",
102102
test: testMinRelayFeeBump,
103103
},
104+
{
105+
name: "zero value anchor sweep",
106+
test: testZeroValueAnchorSweep,
107+
},
104108
{
105109
name: "restart receiver check balance",
106110
test: testRestartReceiverCheckBalance,

itest/zero_value_anchor_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package itest
2+
3+
import (
4+
"context"
5+
6+
"github.com/lightninglabs/taproot-assets/asset"
7+
"github.com/lightninglabs/taproot-assets/taprpc"
8+
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
// testZeroValueAnchorSweep tests that zero-value anchor outputs
13+
// are automatically swept when creating new on-chain transactions.
14+
func testZeroValueAnchorSweep(t *harnessTest) {
15+
ctxb := context.Background()
16+
17+
// First, mint some simple asset.
18+
rpcAssets := MintAssetsConfirmBatch(
19+
t.t, t.lndHarness.Miner().Client, t.tapd,
20+
[]*mintrpc.MintAssetRequest{simpleAssets[0]},
21+
)
22+
genInfo := rpcAssets[0].AssetGenesis
23+
assetAmount := simpleAssets[0].Asset.Amount
24+
25+
// Create a second tapd node.
26+
bobLnd := t.lndHarness.NewNodeWithCoins("Bob", nil)
27+
secondTapd := setupTapdHarness(t.t, t, bobLnd, t.universeServer)
28+
defer func() {
29+
require.NoError(t.t, secondTapd.stop(!*noDelete))
30+
}()
31+
32+
bobAddr, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
33+
AssetId: genInfo.AssetId,
34+
Amt: assetAmount,
35+
AssetVersion: rpcAssets[0].Version,
36+
})
37+
require.NoError(t.t, err)
38+
39+
// Send ALL assets to Bob, which should create a tombstone.
40+
sendResp, _ := sendAssetsToAddr(t, t.tapd, bobAddr)
41+
42+
ConfirmAndAssertOutboundTransfer(
43+
t.t, t.lndHarness.Miner().Client, t.tapd, sendResp,
44+
genInfo.AssetId,
45+
[]uint64{0, assetAmount}, 0, 1,
46+
)
47+
AssertNonInteractiveRecvComplete(t.t, secondTapd, 1)
48+
49+
// Alice should have 1 tombstone UTXO from the full-value send.
50+
AssertBalances(
51+
t.t, t.tapd, 0, WithScriptKeyType(asset.ScriptKeyTombstone),
52+
WithNumUtxos(1), WithNumAnchorUtxos(1),
53+
)
54+
55+
// Test 1: Send transaction sweeps tombstones.
56+
rpcAssets2 := MintAssetsConfirmBatch(
57+
t.t, t.lndHarness.Miner().Client, t.tapd,
58+
[]*mintrpc.MintAssetRequest{simpleAssets[0]},
59+
)
60+
genInfo2 := rpcAssets2[0].AssetGenesis
61+
62+
// Send full amount of the new asset. This should sweep Alice's
63+
// first tombstone and create a new one.
64+
bobAddr2, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
65+
AssetId: genInfo2.AssetId,
66+
Amt: assetAmount,
67+
AssetVersion: rpcAssets2[0].Version,
68+
})
69+
require.NoError(t.t, err)
70+
71+
sendResp2, _ := sendAssetsToAddr(t, t.tapd, bobAddr2)
72+
73+
ConfirmAndAssertOutboundTransfer(
74+
t.t, t.lndHarness.Miner().Client, t.tapd, sendResp2,
75+
genInfo2.AssetId,
76+
[]uint64{0, assetAmount}, 1, 2,
77+
)
78+
AssertNonInteractiveRecvComplete(t.t, secondTapd, 2)
79+
80+
// Check Alice's tombstone balance. The first tombstone should have been
81+
// swept (spent on-chain as an input), and a new one created. We now
82+
// have 1 tombstone UTXO (the new one from the second send).
83+
AssertBalances(
84+
t.t, t.tapd, 0, WithScriptKeyType(asset.ScriptKeyTombstone),
85+
WithNumUtxos(1), WithNumAnchorUtxos(1),
86+
)
87+
88+
// Get the new tombstone outpoint.
89+
utxosAfterSend, err := t.tapd.ListUtxos(ctxb, &taprpc.ListUtxosRequest{
90+
ScriptKeyType: &taprpc.ScriptKeyTypeQuery{
91+
Type: &taprpc.ScriptKeyTypeQuery_ExplicitType{
92+
ExplicitType: taprpc.
93+
ScriptKeyType_SCRIPT_KEY_TOMBSTONE,
94+
},
95+
},
96+
})
97+
require.NoError(t.t, err)
98+
require.Len(t.t, utxosAfterSend.ManagedUtxos, 1)
99+
100+
tombstoneOp2 := ""
101+
for outpoint, utxo := range utxosAfterSend.ManagedUtxos {
102+
if !utxo.Swept {
103+
tombstoneOp2 = outpoint
104+
break
105+
}
106+
}
107+
require.NotEmpty(t.t, tombstoneOp2)
108+
109+
// Test 2: Burning transaction sweeps tombstones.
110+
rpcAssets3 := MintAssetsConfirmBatch(
111+
t.t, t.lndHarness.Miner().Client, t.tapd,
112+
[]*mintrpc.MintAssetRequest{simpleAssets[0]},
113+
)
114+
genInfo3 := rpcAssets3[0].AssetGenesis
115+
116+
// Full burn the asset to create a zero-value burn UTXO
117+
// and sweep the second tombstone.
118+
burnResp, err := t.tapd.BurnAsset(ctxb, &taprpc.BurnAssetRequest{
119+
Asset: &taprpc.BurnAssetRequest_AssetId{
120+
AssetId: genInfo3.AssetId,
121+
},
122+
AmountToBurn: assetAmount,
123+
ConfirmationText: "assets will be destroyed",
124+
})
125+
require.NoError(t.t, err)
126+
127+
AssertAssetOutboundTransferWithOutputs(
128+
t.t, t.lndHarness.Miner().Client, t.tapd, burnResp.BurnTransfer,
129+
[][]byte{genInfo3.AssetId},
130+
[]uint64{assetAmount}, 2, 3, 1, true,
131+
)
132+
133+
// Alice should have 0 tombstones remaining and 1 burn UTXO.
134+
AssertBalances(
135+
t.t, t.tapd, 0, WithScriptKeyType(asset.ScriptKeyTombstone),
136+
WithNumUtxos(0), WithNumAnchorUtxos(0),
137+
)
138+
AssertBalances(
139+
t.t, t.tapd, assetAmount, WithScriptKeyType(asset.ScriptKeyBurn),
140+
WithNumUtxos(1), WithNumAnchorUtxos(1),
141+
)
142+
143+
// Get the burn UTXO outpoint for the next test.
144+
burnUtxos, err := t.tapd.ListUtxos(ctxb, &taprpc.ListUtxosRequest{
145+
ScriptKeyType: &taprpc.ScriptKeyTypeQuery{
146+
Type: &taprpc.ScriptKeyTypeQuery_ExplicitType{
147+
ExplicitType: taprpc.
148+
ScriptKeyType_SCRIPT_KEY_BURN,
149+
},
150+
},
151+
})
152+
require.NoError(t.t, err)
153+
require.Len(t.t, burnUtxos.ManagedUtxos, 1)
154+
155+
burnOutpoint := ""
156+
for outpoint, utxo := range burnUtxos.ManagedUtxos {
157+
if !utxo.Swept {
158+
burnOutpoint = outpoint
159+
break
160+
}
161+
}
162+
require.NotEmpty(t.t, burnOutpoint)
163+
164+
// Test 3: Send transactions sweeps zero-value burns.
165+
rpcAssets4 := MintAssetsConfirmBatch(
166+
t.t, t.lndHarness.Miner().Client, t.tapd,
167+
[]*mintrpc.MintAssetRequest{simpleAssets[0]},
168+
)
169+
genInfo4 := rpcAssets4[0].AssetGenesis
170+
171+
// Send partial amouunt. This should NOT create a tombstone output
172+
// and sweep the burn UTXO.
173+
partialAmount := assetAmount / 2
174+
bobAddr3, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
175+
AssetId: genInfo4.AssetId,
176+
Amt: partialAmount,
177+
AssetVersion: rpcAssets4[0].Version,
178+
})
179+
require.NoError(t.t, err)
180+
181+
sendResp3, _ := sendAssetsToAddr(t, t.tapd, bobAddr3)
182+
183+
ConfirmAndAssertOutboundTransfer(
184+
t.t, t.lndHarness.Miner().Client, t.tapd, sendResp3,
185+
genInfo4.AssetId,
186+
[]uint64{partialAmount, partialAmount}, 3, 4,
187+
)
188+
AssertNonInteractiveRecvComplete(t.t, secondTapd, 3)
189+
190+
// The burn UTXO should have been swept.
191+
AssertBalances(
192+
t.t, t.tapd, 0, WithScriptKeyType(asset.ScriptKeyBurn),
193+
WithNumUtxos(0), WithNumAnchorUtxos(0),
194+
)
195+
}

0 commit comments

Comments
 (0)