Skip to content

Commit e542351

Browse files
committed
itest: add testSendSelectedCoins to check selected utxos
1 parent 0041426 commit e542351

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

itest/list_on_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ var allTestCases = []*lntest.TestCase{
4949
Name: "send all coins",
5050
TestFunc: testSendAllCoins,
5151
},
52+
{
53+
Name: "send selected coins",
54+
TestFunc: testSendSelectedCoins,
55+
},
56+
{
57+
Name: "send selected coins channel reserve",
58+
TestFunc: testSendSelectedCoinsChannelReserve,
59+
},
5260
{
5361
Name: "disconnecting target peer",
5462
TestFunc: testDisconnectingTargetPeer,

itest/lnd_misc_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,3 +1308,195 @@ func testNativeSQLNoMigration(ht *lntest.HarnessTest) {
13081308
alice.SetExtraArgs(nil)
13091309
require.NoError(ht, alice.Start(ht.Context()))
13101310
}
1311+
1312+
// testSendSelectedCoins tests that we're able to properly send the selected
1313+
// coins from the wallet to a single target address.
1314+
func testSendSelectedCoins(ht *lntest.HarnessTest) {
1315+
// First, we'll make a new node, Alice who'll we'll use to test wallet
1316+
// sweeping.
1317+
alice := ht.NewNode("Alice", nil)
1318+
1319+
// Next, we'll give Alice exactly 3 utxos of 1 BTC of different address
1320+
// types.
1321+
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
1322+
ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, alice)
1323+
ht.FundCoinsP2TR(btcutil.SatoshiPerBitcoin, alice)
1324+
1325+
// Get all the utxos in the wallet and assert there are three.
1326+
utxos := ht.AssertNumUTXOs(alice, 3)
1327+
1328+
// Ensure that we can't send duplicate coins.
1329+
//
1330+
// Create duplciate outpoints.
1331+
dupOutpoints := []*lnrpc.OutPoint{
1332+
utxos[0].Outpoint,
1333+
utxos[0].Outpoint,
1334+
}
1335+
1336+
// Send the duplicate outpoints and assert there's an error.
1337+
err := alice.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
1338+
Addr: ht.NewMinerAddress().String(),
1339+
Outpoints: dupOutpoints,
1340+
})
1341+
require.ErrorContains(ht, err, "selected outpoints contain duplicate")
1342+
1343+
// Send a selected coin with a specific amount.
1344+
//
1345+
// We'll send the first utxo with an amount of 0.5 BTC.
1346+
amt := btcutil.Amount(0.5 * btcutil.SatoshiPerBitcoin)
1347+
alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{
1348+
Addr: ht.NewMinerAddress().String(),
1349+
Outpoints: []*lnrpc.OutPoint{
1350+
utxos[0].Outpoint,
1351+
},
1352+
Amount: int64(amt),
1353+
})
1354+
1355+
// We expect to see the above tx in the mempool.
1356+
tx := ht.GetNumTxsFromMempool(1)[0]
1357+
1358+
// Assert the tx has the expected shape. It should have 1 input and 2
1359+
// outputs - the input is the selected UTXO, and the outputs are the
1360+
// specified amount and the change amount.
1361+
require.Len(ht, tx.TxIn, 1)
1362+
require.Len(ht, tx.TxOut, 2)
1363+
1364+
// Check it's using the selected UTXO as input.
1365+
require.Equal(ht, utxos[0].Outpoint.TxidStr,
1366+
tx.TxIn[0].PreviousOutPoint.Hash.String())
1367+
1368+
// Mine a block to confirm the above tx.
1369+
ht.MineBlocksAndAssertNumTxes(1, 1)
1370+
1371+
// We now test we can send the selected coins to a single target
1372+
// address with `SendAll` flag.
1373+
//
1374+
// Get all the utxos in the wallet and assert there are three.
1375+
utxos = ht.AssertNumUTXOs(alice, 3)
1376+
1377+
// Select the first two coins and send them to a single target address.
1378+
alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{
1379+
Addr: ht.NewMinerAddress().String(),
1380+
Outpoints: []*lnrpc.OutPoint{
1381+
utxos[0].Outpoint,
1382+
utxos[1].Outpoint,
1383+
},
1384+
SendAll: true,
1385+
})
1386+
1387+
// We expect to see the above tx in the mempool.
1388+
tx = ht.GetNumTxsFromMempool(1)[0]
1389+
1390+
// Assert the tx has the expected shape. It should have 2 inputs and 1
1391+
// output.
1392+
require.Len(ht, tx.TxIn, 2)
1393+
require.Len(ht, tx.TxOut, 1)
1394+
1395+
// Check it's using the selected UTXOs as inputs.
1396+
prevOutpoint1 := tx.TxIn[0].PreviousOutPoint.Hash.String()
1397+
prevOutpoint2 := tx.TxIn[1].PreviousOutPoint.Hash.String()
1398+
1399+
if prevOutpoint1 == utxos[0].Outpoint.TxidStr {
1400+
require.Equal(ht, utxos[1].Outpoint.TxidStr, prevOutpoint2)
1401+
} else {
1402+
require.Equal(ht, utxos[1].Outpoint.TxidStr, prevOutpoint1)
1403+
require.Equal(ht, utxos[0].Outpoint.TxidStr, prevOutpoint2)
1404+
}
1405+
1406+
// Mine a block to confirm the above tx.
1407+
ht.MineBlocksAndAssertNumTxes(1, 1)
1408+
1409+
// Since the first two UTXOs have been sent to an address outside her
1410+
// wallet, Alice should see a single UTXO now.
1411+
ht.AssertNumUTXOs(alice, 1)
1412+
}
1413+
1414+
// testSendSelectedCoinsChannelReserve tests that if sending selected coins
1415+
// would violate the channel reserve requirement the RPC call will fail. It
1416+
// also checks that change outputs will be created automatically if `SendAll`
1417+
// flag is set.
1418+
func testSendSelectedCoinsChannelReserve(ht *lntest.HarnessTest) {
1419+
chanAmt := btcutil.Amount(100_000)
1420+
1421+
// Create a two-hop network: Alice -> Bob.
1422+
//
1423+
// NOTE: Alice will have one UTXO after the funding.
1424+
_, nodes := createSimpleNetwork(
1425+
ht, []string{"--protocol.anchors"}, 2,
1426+
lntest.OpenChannelParams{
1427+
Amt: chanAmt,
1428+
},
1429+
)
1430+
1431+
alice := nodes[0]
1432+
1433+
// Fund Alice one more UTXO.
1434+
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
1435+
1436+
// Get all the utxos in the wallet and assert there are two:
1437+
// - one from the above `FundCoins`.
1438+
// - one from the change output after the channel funding.
1439+
utxos := ht.AssertNumUTXOs(alice, 2)
1440+
1441+
// Get all the outpoints.
1442+
outpoints := []*lnrpc.OutPoint{
1443+
utxos[0].Outpoint,
1444+
utxos[1].Outpoint,
1445+
}
1446+
1447+
// Calculate the total amount of the two UTXOs.
1448+
totalAmt := utxos[0].AmountSat + utxos[1].AmountSat
1449+
1450+
// Send the total amount of the two UTXOs and expect an error since
1451+
// fees cannot be covered.
1452+
err := alice.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
1453+
Addr: ht.NewMinerAddress().String(),
1454+
Outpoints: outpoints,
1455+
Amount: totalAmt,
1456+
})
1457+
require.ErrorContains(ht, err, "insufficient funds available to "+
1458+
"construct transaction")
1459+
1460+
// Get the required reserve amount.
1461+
resp := alice.RPC.RequiredReserve(
1462+
&walletrpc.RequiredReserveRequest{
1463+
AdditionalPublicChannels: 1,
1464+
},
1465+
)
1466+
1467+
// Calculate an amount which, after sending it, would violate the
1468+
// channel reserve requirement.
1469+
amt := totalAmt - resp.RequiredReserve
1470+
1471+
// Send the amount and expect an error since the channel reserve cannot
1472+
// be covered.
1473+
err = alice.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
1474+
Addr: ht.NewMinerAddress().String(),
1475+
Outpoints: outpoints,
1476+
Amount: amt,
1477+
})
1478+
require.ErrorContains(ht, err, "reserved wallet balance invalidated")
1479+
1480+
// Finally, check that we can send all the selected coins with the help
1481+
// of the `SendAll` flag as it will automatically handle reserving
1482+
// change outputs based on the channel reserve requirements.
1483+
alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{
1484+
Addr: ht.NewMinerAddress().String(),
1485+
Outpoints: outpoints,
1486+
SendAll: true,
1487+
})
1488+
1489+
// We expect to see the above tx in the mempool.
1490+
tx := ht.GetNumTxsFromMempool(1)[0]
1491+
1492+
// Assert the tx has the expected shape. It should have 2 inputs and 2
1493+
// outputs.
1494+
require.Len(ht, tx.TxIn, 2)
1495+
require.Len(ht, tx.TxOut, 2)
1496+
1497+
// Mine a block to confirm the above tx.
1498+
ht.MineBlocksAndAssertNumTxes(1, 1)
1499+
1500+
// Alice should have one reserved UTXO now.
1501+
ht.AssertNumUTXOs(alice, 1)
1502+
}

0 commit comments

Comments
 (0)