Skip to content

Commit 74f0bb1

Browse files
committed
itest: add test for zero-value utxo garbage collection
1 parent 6f6b70f commit 74f0bb1

File tree

2 files changed

+225
-0
lines changed

2 files changed

+225
-0
lines changed

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

0 commit comments

Comments
 (0)