@@ -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