lnd_channel_funding_fund_max_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package itest
  2. import (
  3. "context"
  4. "fmt"
  5. "testing"
  6. "github.com/btcsuite/btcd/btcutil"
  7. "github.com/lightningnetwork/lnd"
  8. "github.com/lightningnetwork/lnd/input"
  9. "github.com/lightningnetwork/lnd/lnrpc"
  10. "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
  11. "github.com/lightningnetwork/lnd/lntest"
  12. "github.com/lightningnetwork/lnd/lntest/node"
  13. "github.com/lightningnetwork/lnd/lnwallet"
  14. "github.com/lightningnetwork/lnd/lnwallet/chainfee"
  15. "github.com/lightningnetwork/lnd/lnwire"
  16. "github.com/stretchr/testify/require"
  17. )
  18. type chanFundMaxTestCase struct {
  19. // name is the name of the target test case.
  20. name string
  21. // initialWalletBalance is the amount in Alice's wallet.
  22. initialWalletBalance btcutil.Amount
  23. // pushAmt is the amount to be pushed to Bob.
  24. pushAmt btcutil.Amount
  25. // feeRate is an optional fee in satoshi/bytes used when opening a
  26. // channel.
  27. feeRate btcutil.Amount
  28. // expectedBalanceAlice is Alice's expected balance in her channel.
  29. expectedBalanceAlice btcutil.Amount
  30. // chanOpenShouldFail denotes if we expect the channel opening to fail.
  31. chanOpenShouldFail bool
  32. // expectedErrStr contains the expected error in case chanOpenShouldFail
  33. // is set to true.
  34. expectedErrStr string
  35. // commitmentType allows to define the exact type when opening the
  36. // channel.
  37. commitmentType lnrpc.CommitmentType
  38. // private denotes if the channel opening is announced to the network or
  39. // not.
  40. private bool
  41. }
  42. // testChannelFundMax checks various channel funding scenarios where the user
  43. // instructed the wallet to use all remaining funds.
  44. func testChannelFundMax(ht *lntest.HarnessTest) {
  45. // Create two new nodes that open a channel between each other for these
  46. // tests.
  47. args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
  48. alice := ht.NewNode("Alice", args)
  49. defer ht.Shutdown(alice)
  50. bob := ht.NewNode("Bob", args)
  51. defer ht.Shutdown(bob)
  52. // Ensure both sides are connected so the funding flow can be properly
  53. // executed.
  54. ht.EnsureConnected(alice, bob)
  55. // Calculate reserve amount for one channel.
  56. reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
  57. context.Background(), &walletrpc.RequiredReserveRequest{
  58. AdditionalPublicChannels: 1,
  59. },
  60. )
  61. reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
  62. var testCases = []*chanFundMaxTestCase{
  63. {
  64. name: "wallet amount is dust",
  65. initialWalletBalance: 2_000,
  66. chanOpenShouldFail: true,
  67. feeRate: 20,
  68. expectedErrStr: "output amount(-0.00000435 BTC) " +
  69. "after subtracting fees(0.00002435 BTC) " +
  70. "below dust limit(0.00000330 BTC)",
  71. },
  72. {
  73. name: "wallet amount < min chan size " +
  74. "(~18000sat)",
  75. initialWalletBalance: 18_000,
  76. // Using a feeRate of 1 sat/vByte ensures that we test
  77. // for min chan size and not excessive fees.
  78. feeRate: 1,
  79. chanOpenShouldFail: true,
  80. expectedErrStr: "available funds(0.00017877 BTC) " +
  81. "below the minimum amount(0.00020000 BTC)",
  82. },
  83. {
  84. name: "wallet amount > min chan " +
  85. "size (37000sat)",
  86. initialWalletBalance: 37_000,
  87. // The transaction fee to open the channel must be
  88. // subtracted from Alice's balance.
  89. // (since wallet balance < max-chan-size)
  90. expectedBalanceAlice: btcutil.Amount(37_000) -
  91. fundingFee(1, false),
  92. },
  93. {
  94. name: "wallet amount > max chan size " +
  95. "(20000000sat)",
  96. initialWalletBalance: 20_000_000,
  97. expectedBalanceAlice: lnd.MaxFundingAmount,
  98. },
  99. // Expects, that if the maximum funding amount for a channel is
  100. // pushed to the remote side, then the funding flow is failing
  101. // because the push amount has to be less than the local channel
  102. // amount.
  103. {
  104. name: "wallet amount > max chan size, " +
  105. "push amount == max-chan-size",
  106. initialWalletBalance: 20_000_000,
  107. pushAmt: lnd.MaxFundingAmount,
  108. chanOpenShouldFail: true,
  109. expectedErrStr: "amount pushed to remote peer for " +
  110. "initial state must be below the local " +
  111. "funding amount",
  112. },
  113. // Expects that if the maximum funding amount for a channel is
  114. // pushed to the remote side then the funding flow is failing
  115. // due to insufficient funds in the local balance to cover for
  116. // fees in the channel opening. By that the test also ensures
  117. // that the fees are not covered by the remaining wallet
  118. // balance.
  119. {
  120. name: "wallet amount > max chan size, " +
  121. "push amount == max-chan-size - 1_000",
  122. initialWalletBalance: 20_000_000,
  123. pushAmt: lnd.MaxFundingAmount - 1_000,
  124. chanOpenShouldFail: true,
  125. expectedErrStr: "funder balance too small (-8050000) " +
  126. "with fee=9050 sat, minimum=708 sat required",
  127. },
  128. {
  129. name: "wallet amount > max chan size, " +
  130. "push amount 16766000",
  131. initialWalletBalance: 20_000_000,
  132. pushAmt: 16_766_000,
  133. expectedBalanceAlice: lnd.MaxFundingAmount - 16_766_000,
  134. },
  135. {
  136. name: "anchor reserved value",
  137. initialWalletBalance: 100_000,
  138. commitmentType: lnrpc.CommitmentType_ANCHORS,
  139. expectedBalanceAlice: btcutil.Amount(100_000) -
  140. fundingFee(1, true) - reserveAmount,
  141. },
  142. // Funding a private anchor channel should omit the achor
  143. // reserve and produce no change output.
  144. {
  145. name: "private anchor no reserved " +
  146. "value",
  147. private: true,
  148. initialWalletBalance: 100_000,
  149. commitmentType: lnrpc.CommitmentType_ANCHORS,
  150. expectedBalanceAlice: btcutil.Amount(100_000) -
  151. fundingFee(1, false),
  152. },
  153. }
  154. for _, testCase := range testCases {
  155. success := ht.Run(
  156. testCase.name, func(tt *testing.T) {
  157. runFundMaxTestCase(
  158. ht, alice, bob, testCase, reserveAmount,
  159. )
  160. },
  161. )
  162. // Stop at the first failure. Mimic behavior of original test
  163. // framework.
  164. if !success {
  165. break
  166. }
  167. }
  168. }
  169. // runTestCase runs a single test case asserting that test conditions are met.
  170. func runFundMaxTestCase(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
  171. testCase *chanFundMaxTestCase, reserveAmount btcutil.Amount) {
  172. ht.FundCoins(testCase.initialWalletBalance, alice)
  173. defer func() {
  174. if testCase.initialWalletBalance <= 2_000 {
  175. // Add additional funds to sweep "dust" UTXO.
  176. ht.FundCoins(100_000, alice)
  177. }
  178. // Remove all funds from Alice.
  179. sweepNodeWalletAndAssert(ht, alice)
  180. }()
  181. commitType := testCase.commitmentType
  182. if commitType == lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE {
  183. commitType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
  184. }
  185. // The parameters to try opening the channel with.
  186. chanParams := lntest.OpenChannelParams{
  187. Amt: 0,
  188. PushAmt: testCase.pushAmt,
  189. SatPerVByte: testCase.feeRate,
  190. CommitmentType: commitType,
  191. FundMax: true,
  192. Private: testCase.private,
  193. }
  194. // If we don't expect the channel opening to be
  195. // successful, simply check for an error.
  196. if testCase.chanOpenShouldFail {
  197. expectedErr := fmt.Errorf(testCase.expectedErrStr)
  198. ht.OpenChannelAssertErr(
  199. alice, bob, chanParams, expectedErr,
  200. )
  201. return
  202. }
  203. // Otherwise, if we expect to open a channel use the helper function.
  204. chanPoint := ht.OpenChannel(alice, bob, chanParams)
  205. // Close the channel between Alice and Bob, asserting
  206. // that the channel has been properly closed on-chain.
  207. defer ht.CloseChannel(alice, chanPoint)
  208. cType := ht.GetChannelCommitType(alice, chanPoint)
  209. // Alice's balance should be her amount subtracted by the commitment
  210. // transaction fee.
  211. checkChannelBalance(
  212. ht, alice,
  213. testCase.expectedBalanceAlice-lntest.CalcStaticFee(cType, 0),
  214. testCase.pushAmt,
  215. )
  216. // Ensure Bob's balance within the channel is equal to the push amount.
  217. checkChannelBalance(
  218. ht, bob, testCase.pushAmt,
  219. testCase.expectedBalanceAlice-lntest.CalcStaticFee(cType, 0),
  220. )
  221. if lntest.CommitTypeHasAnchors(testCase.commitmentType) &&
  222. !testCase.private {
  223. ht.AssertWalletAccountBalance(
  224. alice, lnwallet.DefaultAccountName,
  225. int64(reserveAmount), 0,
  226. )
  227. }
  228. }
  229. // Creates a helper closure to be used below which asserts the proper
  230. // response to a channel balance RPC.
  231. func checkChannelBalance(ht *lntest.HarnessTest, node *node.HarnessNode,
  232. local, remote btcutil.Amount) {
  233. expectedResponse := &lnrpc.ChannelBalanceResponse{
  234. LocalBalance: &lnrpc.Amount{
  235. Sat: uint64(local),
  236. Msat: uint64(lnwire.NewMSatFromSatoshis(local)),
  237. },
  238. RemoteBalance: &lnrpc.Amount{
  239. Sat: uint64(remote),
  240. Msat: uint64(lnwire.NewMSatFromSatoshis(
  241. remote,
  242. )),
  243. },
  244. UnsettledLocalBalance: &lnrpc.Amount{},
  245. UnsettledRemoteBalance: &lnrpc.Amount{},
  246. PendingOpenLocalBalance: &lnrpc.Amount{},
  247. PendingOpenRemoteBalance: &lnrpc.Amount{},
  248. // Deprecated fields.
  249. Balance: int64(local),
  250. }
  251. ht.AssertChannelBalanceResp(node, expectedResponse)
  252. }
  253. // fundingFee returns the fee estimate used for a tx with the given number of
  254. // inputs and the optional change output. This matches the estimate done by the
  255. // wallet.
  256. func fundingFee(numInput int, change bool) btcutil.Amount {
  257. var weightEstimate input.TxWeightEstimator
  258. // The standard fee rate used for a funding transaction.
  259. var feeRate = chainfee.SatPerKWeight(12500)
  260. // All inputs.
  261. for i := 0; i < numInput; i++ {
  262. weightEstimate.AddP2WKHInput()
  263. }
  264. // The multisig funding output.
  265. weightEstimate.AddP2WSHOutput()
  266. // Optionally count a change output.
  267. if change {
  268. weightEstimate.AddP2TROutput()
  269. }
  270. totalWeight := int64(weightEstimate.Weight())
  271. return feeRate.FeeForWeight(totalWeight)
  272. }
  273. // sweepNodeWalletAndAssert sweeps funds from a node wallet.
  274. func sweepNodeWalletAndAssert(ht *lntest.HarnessTest, node *node.HarnessNode) {
  275. // New miner address we will sweep all funds to.
  276. minerAddr, err := ht.Miner.NewAddress()
  277. require.NoError(ht, err)
  278. // Send all funds back to the miner node.
  279. node.RPC.SendCoins(&lnrpc.SendCoinsRequest{
  280. Addr: minerAddr.String(),
  281. SendAll: true,
  282. TargetConf: 6,
  283. })
  284. // Ensures we don't leave any transaction in the mempool after sweeping.
  285. ht.MineBlocksAndAssertNumTxes(1, 1)
  286. // Ensure that the node's balance is 0
  287. checkChannelBalance(ht, node, 0, 0)
  288. }