• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12027808511

26 Nov 2024 08:58AM UTC coverage: 59.095% (+1.0%) from 58.071%
12027808511

Pull #9307

github

yyforyongyu
lntest: fix flakeness in `openChannelsForNodes`

We now make sure the channel participants have heard their private
channel when opening channels.
Pull Request #9307: Beat itest [2/3]: document and fix itest flakes

1 of 26 new or added lines in 4 files covered. (3.85%)

20 existing lines in 7 files now uncovered.

133850 of 226499 relevant lines covered (59.1%)

19519.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.54
/lnwallet/test/test_interface.go
1
package lnwallettest
2

3
import (
4
        "bytes"
5
        "crypto/sha256"
6
        "encoding/hex"
7
        "fmt"
8
        "net"
9
        "path/filepath"
10
        "reflect"
11
        "runtime"
12
        "strings"
13
        "testing"
14
        "time"
15

16
        "github.com/btcsuite/btcd/blockchain"
17
        "github.com/btcsuite/btcd/btcec/v2"
18
        "github.com/btcsuite/btcd/btcjson"
19
        "github.com/btcsuite/btcd/btcutil"
20
        "github.com/btcsuite/btcd/chaincfg"
21
        "github.com/btcsuite/btcd/chaincfg/chainhash"
22
        "github.com/btcsuite/btcd/integration/rpctest"
23
        "github.com/btcsuite/btcd/mempool"
24
        "github.com/btcsuite/btcd/rpcclient"
25
        "github.com/btcsuite/btcd/txscript"
26
        "github.com/btcsuite/btcd/wire"
27
        "github.com/btcsuite/btcwallet/chain"
28
        "github.com/btcsuite/btcwallet/wallet"
29
        "github.com/btcsuite/btcwallet/walletdb"
30
        _ "github.com/btcsuite/btcwallet/walletdb/bdb"
31
        "github.com/davecgh/go-spew/spew"
32
        "github.com/lightninglabs/neutrino"
33
        "github.com/lightningnetwork/lnd/blockcache"
34
        "github.com/lightningnetwork/lnd/chainntnfs"
35
        "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
36
        "github.com/lightningnetwork/lnd/channeldb"
37
        "github.com/lightningnetwork/lnd/fn"
38
        "github.com/lightningnetwork/lnd/input"
39
        "github.com/lightningnetwork/lnd/keychain"
40
        "github.com/lightningnetwork/lnd/kvdb"
41
        "github.com/lightningnetwork/lnd/labels"
42
        "github.com/lightningnetwork/lnd/lntest/unittest"
43
        "github.com/lightningnetwork/lnd/lnwallet"
44
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
45
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
46
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
47
        "github.com/lightningnetwork/lnd/lnwire"
48
        "github.com/stretchr/testify/require"
49
)
50

51
var (
52
        bobsPrivKey = []byte{
53
                0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
54
                0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
55
                0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
56
                0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
57
        }
58

59
        // Use a hard-coded HD seed.
60
        testHdSeed = chainhash.Hash{
61
                0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
62
                0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
63
                0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
64
                0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
65
        }
66

67
        aliceHDSeed = chainhash.Hash{
68
                0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
69
                0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
70
                0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
71
                0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
72
        }
73
        bobHDSeed = chainhash.Hash{
74
                0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
75
                0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
76
                0x4f, 0x2f, 0x6f, 0x25, 0x98, 0xa3, 0xef, 0xb9,
77
                0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
78
        }
79

80
        netParams = &chaincfg.RegressionNetParams
81
        chainHash = netParams.GenesisHash
82

83
        _, alicePub = btcec.PrivKeyFromBytes(testHdSeed[:])
84
        _, bobPub   = btcec.PrivKeyFromBytes(bobsPrivKey)
85

86
        // The number of confirmations required to consider any created channel
87
        // open.
88
        numReqConfs uint16 = 1
89

90
        csvDelay uint16 = 4
91

92
        bobAddr, _   = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
93
        aliceAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.3:9000")
94

95
        defaultMaxLocalCsvDelay uint16 = 10000
96
)
97

98
// assertProperBalance asserts than the total value of the unspent outputs
99
// within the wallet are *exactly* amount. If unable to retrieve the current
100
// balance, or the assertion fails, the test will halt with a fatal error.
101
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet,
102
        numConfirms int32, amount float64) {
8✔
103

8✔
104
        balance, err := lw.ConfirmedBalance(numConfirms, lnwallet.DefaultAccountName)
8✔
105
        require.NoError(t, err, "unable to query for balance")
8✔
106
        if balance.ToBTC() != amount {
8✔
107
                t.Fatalf("wallet credits not properly loaded, should have 40BTC, "+
×
108
                        "instead have %v", balance)
×
109
        }
×
110
}
111

112
func assertReservationDeleted(res *lnwallet.ChannelReservation, t *testing.T) {
32✔
113
        if err := res.Cancel(); err == nil {
32✔
114
                t.Fatalf("reservation wasn't deleted from wallet")
×
115
        }
×
116
}
117

118
// mineAndAssertTxInBlock asserts that a transaction is included within the next
119
// block mined.
120
func mineAndAssertTxInBlock(t *testing.T, miner *rpctest.Harness,
121
        txid chainhash.Hash) {
34✔
122

34✔
123
        t.Helper()
34✔
124

34✔
125
        // First, we'll wait for the transaction to arrive in the mempool.
34✔
126
        if err := waitForMempoolTx(miner, &txid); err != nil {
34✔
127
                t.Fatalf("unable to find %v in the mempool: %v", txid, err)
×
128
        }
×
129

130
        // We'll mined a block to confirm it.
131
        blockHashes, err := miner.Client.Generate(1)
34✔
132
        require.NoError(t, err, "unable to generate new block")
34✔
133

34✔
134
        // Finally, we'll check it was actually mined in this block.
34✔
135
        block, err := miner.Client.GetBlock(blockHashes[0])
34✔
136
        if err != nil {
34✔
137
                t.Fatalf("unable to get block %v: %v", blockHashes[0], err)
×
138
        }
×
139
        if len(block.Transactions) != 2 {
34✔
140
                t.Fatalf("expected 2 transactions in block, found %d",
×
141
                        len(block.Transactions))
×
142
        }
×
143
        txHash := block.Transactions[1].TxHash()
34✔
144
        if txHash != txid {
34✔
145
                t.Fatalf("expected transaction %v to be mined, found %v", txid,
×
146
                        txHash)
×
147
        }
×
148
}
149

150
// newPkScript generates a new public key script of the given address type.
151
func newPkScript(t *testing.T, w *lnwallet.LightningWallet,
152
        addrType lnwallet.AddressType) []byte {
18✔
153

18✔
154
        t.Helper()
18✔
155

18✔
156
        addr, err := w.NewAddress(addrType, false, lnwallet.DefaultAccountName)
18✔
157
        require.NoError(t, err, "unable to create new address")
18✔
158
        pkScript, err := txscript.PayToAddrScript(addr)
18✔
159
        require.NoError(t, err, "unable to create output script")
18✔
160

18✔
161
        return pkScript
18✔
162
}
18✔
163

164
// sendCoins is a helper function that encompasses all the things needed for two
165
// parties to send on-chain funds to each other.
166
func sendCoins(t *testing.T, miner *rpctest.Harness,
167
        sender, receiver *lnwallet.LightningWallet, output *wire.TxOut,
168
        feeRate chainfee.SatPerKWeight, mineBlock bool, minConf int32) *wire.MsgTx { //nolint:unparam
40✔
169

40✔
170
        t.Helper()
40✔
171

40✔
172
        tx, err := sender.SendOutputs(
40✔
173
                nil, []*wire.TxOut{output}, feeRate, minConf, labels.External,
40✔
174
                sender.Cfg.CoinSelectionStrategy,
40✔
175
        )
40✔
176
        require.NoError(t, err, "unable to send transaction")
40✔
177

40✔
178
        if mineBlock {
74✔
179
                mineAndAssertTxInBlock(t, miner, tx.TxHash())
34✔
180
        }
34✔
181

182
        if err := waitForWalletSync(miner, sender); err != nil {
40✔
183
                t.Fatalf("unable to sync alice: %v", err)
×
184
        }
×
185
        if err := waitForWalletSync(miner, receiver); err != nil {
40✔
186
                t.Fatalf("unable to sync bob: %v", err)
×
187
        }
×
188

189
        return tx
40✔
190
}
191

192
// assertTxInWallet asserts that a transaction exists in the wallet with the
193
// expected confirmation status.
194
func assertTxInWallet(t *testing.T, w *lnwallet.LightningWallet,
195
        txHash chainhash.Hash, confirmed bool) {
64✔
196

64✔
197
        t.Helper()
64✔
198

64✔
199
        // We'll fetch all of our transaction and go through each one until
64✔
200
        // finding the expected transaction with its expected confirmation
64✔
201
        // status.
64✔
202
        txs, _, _, err := w.ListTransactionDetails(
64✔
203
                0, btcwallet.UnconfirmedHeight, "", 0, 1000,
64✔
204
        )
64✔
205
        require.NoError(t, err, "unable to retrieve transactions")
64✔
206
        for _, tx := range txs {
2,040✔
207
                if tx.Hash != txHash {
3,888✔
208
                        continue
1,912✔
209
                }
210
                if tx.NumConfirmations <= 0 && confirmed {
64✔
211
                        t.Fatalf("expected transaction %v to be confirmed",
×
212
                                txHash)
×
213
                }
×
214
                if tx.NumConfirmations > 0 && !confirmed {
64✔
215
                        t.Fatalf("expected transaction %v to be unconfirmed",
×
216
                                txHash)
×
217
                }
×
218

219
                // We've found the transaction and it matches the desired
220
                // confirmation status, so we can exit.
221
                return
64✔
222
        }
223

224
        t.Fatalf("transaction %v not found", txHash)
×
225
}
226

227
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
228
        numOutputs int, btcPerOutput float64) error {
20✔
229

20✔
230
        // For initial neutrino connection, wait a second.
20✔
231
        // TODO(aakselrod): Eliminate the need for this.
20✔
232
        switch w.BackEnd() {
20✔
233
        case "neutrino":
5✔
234
                time.Sleep(time.Second)
5✔
235
        }
236
        // Using the mining node, spend from a coinbase output numOutputs to
237
        // give us btcPerOutput with each output.
238
        satoshiPerOutput, err := btcutil.NewAmount(btcPerOutput)
20✔
239
        if err != nil {
20✔
240
                return fmt.Errorf("unable to create amt: %w", err)
×
241
        }
×
242
        expectedBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
20✔
243
        if err != nil {
20✔
244
                return err
×
245
        }
×
246
        expectedBalance += btcutil.Amount(int64(satoshiPerOutput) * int64(numOutputs))
20✔
247
        addrs := make([]btcutil.Address, 0, numOutputs)
20✔
248
        for i := 0; i < numOutputs; i++ {
420✔
249
                // Grab a fresh address from the wallet to house this output.
400✔
250
                walletAddr, err := w.NewAddress(
400✔
251
                        lnwallet.WitnessPubKey, false,
400✔
252
                        lnwallet.DefaultAccountName,
400✔
253
                )
400✔
254
                if err != nil {
400✔
255
                        return err
×
256
                }
×
257

258
                script, err := txscript.PayToAddrScript(walletAddr)
400✔
259
                if err != nil {
400✔
260
                        return err
×
261
                }
×
262

263
                addrs = append(addrs, walletAddr)
400✔
264

400✔
265
                output := &wire.TxOut{
400✔
266
                        Value:    int64(satoshiPerOutput),
400✔
267
                        PkScript: script,
400✔
268
                }
400✔
269
                if _, err := miner.SendOutputs([]*wire.TxOut{output}, 2500); err != nil {
400✔
270
                        return err
×
271
                }
×
272
        }
273

274
        // TODO(roasbeef): shouldn't hardcode 10, use config param that dictates
275
        // how many confs we wait before opening a channel.
276
        // Generate 10 blocks with the mining node, this should mine all
277
        // numOutputs transactions created above. We generate 10 blocks here
278
        // in order to give all the outputs a "sufficient" number of confirmations.
279
        if _, err := miner.Client.Generate(10); err != nil {
20✔
280
                return err
×
281
        }
×
282

283
        // Wait until the wallet has finished syncing up to the main chain.
284
        ticker := time.NewTicker(100 * time.Millisecond)
20✔
285
        timeout := time.After(30 * time.Second)
20✔
286

20✔
287
        for range ticker.C {
75✔
288
                balance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
55✔
289
                if err != nil {
55✔
290
                        return err
×
291
                }
×
292
                if balance == expectedBalance {
75✔
293
                        break
20✔
294
                }
295
                select {
35✔
296
                case <-timeout:
×
297
                        synced, _, err := w.IsSynced()
×
298
                        if err != nil {
×
299
                                return err
×
300
                        }
×
301
                        return fmt.Errorf("timed out after 30 seconds "+
×
302
                                "waiting for balance %v, current balance %v, "+
×
303
                                "synced: %t", expectedBalance, balance, synced)
×
304
                default:
35✔
305
                }
306
        }
307
        ticker.Stop()
20✔
308

20✔
309
        return nil
20✔
310
}
311

312
// createTestWallet creates a test LightningWallet will a total of 20BTC
313
// available for funding channels.
314
func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
315
        netParams *chaincfg.Params, notifier chainntnfs.ChainNotifier,
316
        wc lnwallet.WalletController, keyRing keychain.SecretKeyRing,
317
        signer input.Signer, bio lnwallet.BlockChainIO) (*lnwallet.LightningWallet, error) {
8✔
318

8✔
319
        dbDir := filepath.Join(tempTestDir, "cdb")
8✔
320
        fullDB, err := channeldb.Open(dbDir)
8✔
321
        if err != nil {
8✔
322
                return nil, err
×
323
        }
×
324

325
        cfg := lnwallet.Config{
8✔
326
                Database:              fullDB.ChannelStateDB(),
8✔
327
                Notifier:              notifier,
8✔
328
                SecretKeyRing:         keyRing,
8✔
329
                WalletController:      wc,
8✔
330
                Signer:                signer,
8✔
331
                ChainIO:               bio,
8✔
332
                FeeEstimator:          chainfee.NewStaticEstimator(2500, 0),
8✔
333
                NetParams:             *netParams,
8✔
334
                CoinSelectionStrategy: wallet.CoinSelectionLargest,
8✔
335
        }
8✔
336

8✔
337
        wallet, err := lnwallet.NewLightningWallet(cfg)
8✔
338
        if err != nil {
8✔
339
                return nil, err
×
340
        }
×
341

342
        if err := wallet.Startup(); err != nil {
8✔
343
                return nil, err
×
344
        }
×
345

346
        // Load our test wallet with 20 outputs each holding 4BTC.
347
        if err := loadTestCredits(miningNode, wallet, 20, 4); err != nil {
8✔
348
                return nil, err
×
349
        }
×
350

351
        return wallet, nil
8✔
352
}
353

354
func testGetRecoveryInfo(miner *rpctest.Harness,
355
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
356

4✔
357
        // alice's wallet is in recovery mode
4✔
358
        expectedRecoveryMode := true
4✔
359
        expectedProgress := float64(1)
4✔
360

4✔
361
        isRecoveryMode, progress, err := alice.GetRecoveryInfo()
4✔
362
        require.NoError(t, err, "unable to get alice's recovery info")
4✔
363

4✔
364
        require.Equal(t,
4✔
365
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
366
        )
4✔
367
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
368

4✔
369
        // Generate 5 blocks and check the recovery process again.
4✔
370
        const numBlocksMined = 5
4✔
371
        _, err = miner.Client.Generate(numBlocksMined)
4✔
372
        require.NoError(t, err, "unable to mine blocks")
4✔
373

4✔
374
        // Check the recovery process. Once synced, the progress should be 1.
4✔
375
        err = waitForWalletSync(miner, alice)
4✔
376
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
377

4✔
378
        isRecoveryMode, progress, err = alice.GetRecoveryInfo()
4✔
379
        require.NoError(t, err, "unable to get alice's recovery info")
4✔
380

4✔
381
        require.Equal(t,
4✔
382
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
383
        )
4✔
384
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
385

4✔
386
        // bob's wallet is not in recovery mode
4✔
387
        expectedRecoveryMode = false
4✔
388
        expectedProgress = float64(0)
4✔
389

4✔
390
        isRecoveryMode, progress, err = bob.GetRecoveryInfo()
4✔
391
        require.NoError(t, err, "unable to get bob's recovery info")
4✔
392

4✔
393
        require.Equal(t,
4✔
394
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
395
        )
4✔
396
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
397
}
4✔
398

399
func testDualFundingReservationWorkflow(miner *rpctest.Harness,
400
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
3✔
401

3✔
402
        t.Skipf("dual funding isn't exposed on the p2p layer")
3✔
403

3✔
404
        fundingAmount, err := btcutil.NewAmount(5)
3✔
405
        require.NoError(t, err, "unable to create amt")
3✔
406

3✔
407
        // In this scenario, we'll test a dual funder reservation, with each
3✔
408
        // side putting in 10 BTC.
3✔
409

3✔
410
        // Alice initiates a channel funded with 5 BTC for each side, so 10 BTC
3✔
411
        // total. She also generates 2 BTC in change.
3✔
412
        feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
3✔
413
        require.NoError(t, err, "unable to query fee estimator")
3✔
414
        aliceReq := &lnwallet.InitFundingReserveMsg{
3✔
415
                ChainHash:        chainHash,
3✔
416
                NodeID:           bobPub,
3✔
417
                NodeAddr:         bobAddr,
3✔
418
                LocalFundingAmt:  fundingAmount,
3✔
419
                RemoteFundingAmt: fundingAmount,
3✔
420
                CommitFeePerKw:   feePerKw,
3✔
421
                FundingFeePerKw:  feePerKw,
3✔
422
                PushMSat:         0,
3✔
423
                Flags:            lnwire.FFAnnounceChannel,
3✔
424
        }
3✔
425
        aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
3✔
426
        require.NoError(t, err, "unable to initialize funding reservation")
3✔
427
        aliceChanReservation.SetNumConfsRequired(numReqConfs)
3✔
428
        bounds := &channeldb.ChannelStateBounds{
3✔
429
                ChanReserve:      fundingAmount / 100,
3✔
430
                MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmount),
3✔
431
                MinHTLC:          1,
3✔
432
                MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
3✔
433
        }
3✔
434
        commitParams := &channeldb.CommitmentParams{
3✔
435
                DustLimit: lnwallet.DustLimitUnknownWitness(),
3✔
436
                CsvDelay:  csvDelay,
3✔
437
        }
3✔
438
        err = aliceChanReservation.CommitConstraints(
3✔
439
                bounds, commitParams, defaultMaxLocalCsvDelay, false,
3✔
440
        )
3✔
441
        require.NoError(t, err, "unable to verify constraints")
3✔
442

3✔
443
        // The channel reservation should now be populated with a multi-sig key
3✔
444
        // from our HD chain, a change output with 3 BTC, and 2 outputs
3✔
445
        // selected of 4 BTC each. Additionally, the rest of the items needed
3✔
446
        // to fulfill a funding contribution should also have been filled in.
3✔
447
        aliceContribution := aliceChanReservation.OurContribution()
3✔
448
        if len(aliceContribution.Inputs) != 2 {
3✔
449
                t.Fatalf("outputs for funding tx not properly selected, have %v "+
×
450
                        "outputs should have 2", len(aliceContribution.Inputs))
×
451
        }
×
452
        assertContributionInitPopulated(t, aliceContribution)
×
453

×
454
        // Bob does the same, generating his own contribution. He then also
×
455
        // receives' Alice's contribution, and consumes that so we can continue
×
456
        // the funding process.
×
457
        bobReq := &lnwallet.InitFundingReserveMsg{
×
458
                ChainHash:        chainHash,
×
459
                NodeID:           alicePub,
×
460
                NodeAddr:         aliceAddr,
×
461
                LocalFundingAmt:  fundingAmount,
×
462
                RemoteFundingAmt: fundingAmount,
×
463
                CommitFeePerKw:   feePerKw,
×
464
                FundingFeePerKw:  feePerKw,
×
465
                PushMSat:         0,
×
466
                Flags:            lnwire.FFAnnounceChannel,
×
467
        }
×
468
        bobChanReservation, err := bob.InitChannelReservation(bobReq)
×
469
        require.NoError(t, err, "bob unable to init channel reservation")
×
470
        err = bobChanReservation.CommitConstraints(
×
471
                bounds, commitParams, defaultMaxLocalCsvDelay, true,
×
472
        )
×
473
        require.NoError(t, err, "unable to verify constraints")
×
474
        bobChanReservation.SetNumConfsRequired(numReqConfs)
×
475

×
476
        assertContributionInitPopulated(t, bobChanReservation.OurContribution())
×
477

×
478
        err = bobChanReservation.ProcessContribution(aliceContribution)
×
479
        require.NoError(t, err, "bob unable to process alice's contribution")
×
480
        assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
×
481

×
482
        bobContribution := bobChanReservation.OurContribution()
×
483

×
484
        // Bob then sends over his contribution, which will be consumed by
×
485
        // Alice. After this phase, Alice should have all the necessary
×
486
        // material required to craft the funding transaction and commitment
×
487
        // transactions.
×
488
        err = aliceChanReservation.ProcessContribution(bobContribution)
×
489
        require.NoError(t, err, "alice unable to process bob's contribution")
×
490
        assertContributionInitPopulated(t, aliceChanReservation.TheirContribution())
×
491

×
492
        // At this point, all Alice's signatures should be fully populated.
×
493
        aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
×
494
        if aliceFundingSigs == nil {
×
495
                t.Fatalf("alice's funding signatures not populated")
×
496
        }
×
497
        if aliceCommitSig == nil {
×
498
                t.Fatalf("alice's commit signatures not populated")
×
499
        }
×
500

501
        // Additionally, Bob's signatures should also be fully populated.
502
        bobFundingSigs, bobCommitSig := bobChanReservation.OurSignatures()
×
503
        if bobFundingSigs == nil {
×
504
                t.Fatalf("bob's funding signatures not populated")
×
505
        }
×
506
        if bobCommitSig == nil {
×
507
                t.Fatalf("bob's commit signatures not populated")
×
508
        }
×
509

510
        // To conclude, we'll consume first Alice's signatures with Bob, and
511
        // then the other way around.
512
        _, err = aliceChanReservation.CompleteReservation(
×
513
                bobFundingSigs, bobCommitSig,
×
514
        )
×
515
        if err != nil {
×
516
                for _, in := range aliceChanReservation.FinalFundingTx().TxIn {
×
517
                        fmt.Println(in.PreviousOutPoint.String())
×
518
                }
×
519
                t.Fatalf("unable to consume alice's sigs: %v", err)
×
520
        }
521
        _, err = bobChanReservation.CompleteReservation(
×
522
                aliceFundingSigs, aliceCommitSig,
×
523
        )
×
524
        require.NoError(t, err, "unable to consume bob's sigs")
×
525

×
526
        // At this point, the funding tx should have been populated.
×
527
        fundingTx := aliceChanReservation.FinalFundingTx()
×
528
        if fundingTx == nil {
×
529
                t.Fatalf("funding transaction never created!")
×
530
        }
×
531

532
        // The resulting active channel state should have been persisted to the
533
        // DB.
534
        fundingSha := fundingTx.TxHash()
×
535
        aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
×
536
        require.NoError(t, err, "unable to retrieve channel from DB")
×
537
        if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
×
538
                t.Fatalf("channel state not properly saved")
×
539
        }
×
540
        if !aliceChannels[0].ChanType.IsDualFunder() {
×
541
                t.Fatalf("channel not detected as dual funder")
×
542
        }
×
543
        bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
×
544
        require.NoError(t, err, "unable to retrieve channel from DB")
×
545
        if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
×
546
                t.Fatalf("channel state not properly saved")
×
547
        }
×
548
        if !bobChannels[0].ChanType.IsDualFunder() {
×
549
                t.Fatalf("channel not detected as dual funder")
×
550
        }
×
551

552
        // Let Alice publish the funding transaction.
553
        err = alice.PublishTransaction(fundingTx, "")
×
554
        require.NoError(t, err, "unable to publish funding tx")
×
555

×
556
        // Mine a single block, the funding transaction should be included
×
557
        // within this block.
×
558
        err = waitForMempoolTx(miner, &fundingSha)
×
559
        require.NoError(t, err, "tx not relayed to miner")
×
560
        blockHashes, err := miner.Client.Generate(1)
×
561
        require.NoError(t, err, "unable to generate block")
×
562
        block, err := miner.Client.GetBlock(blockHashes[0])
×
563
        require.NoError(t, err, "unable to find block")
×
564
        if len(block.Transactions) != 2 {
×
565
                t.Fatalf("funding transaction wasn't mined: %v", err)
×
566
        }
×
567
        blockTx := block.Transactions[1]
×
568
        if blockTx.TxHash() != fundingSha {
×
569
                t.Fatalf("incorrect transaction was mined")
×
570
        }
×
571

572
        assertReservationDeleted(aliceChanReservation, t)
×
573
        assertReservationDeleted(bobChanReservation, t)
×
574

×
575
        // Wait for wallets to catch up to prevent issues in subsequent tests.
×
576
        err = waitForWalletSync(miner, alice)
×
577
        require.NoError(t, err, "unable to sync alice")
×
578
        err = waitForWalletSync(miner, bob)
×
579
        require.NoError(t, err, "unable to sync bob")
×
580
}
581

582
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
583
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
584

4✔
585
        // Create a single channel asking for 16 BTC total.
4✔
586
        fundingAmount, err := btcutil.NewAmount(8)
4✔
587
        require.NoError(t, err, "unable to create amt")
4✔
588
        feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
4✔
589
        require.NoError(t, err, "unable to query fee estimator")
4✔
590
        req := &lnwallet.InitFundingReserveMsg{
4✔
591
                ChainHash:        chainHash,
4✔
592
                NodeID:           bobPub,
4✔
593
                NodeAddr:         bobAddr,
4✔
594
                LocalFundingAmt:  fundingAmount,
4✔
595
                RemoteFundingAmt: 0,
4✔
596
                CommitFeePerKw:   feePerKw,
4✔
597
                FundingFeePerKw:  feePerKw,
4✔
598
                PushMSat:         0,
4✔
599
                Flags:            lnwire.FFAnnounceChannel,
4✔
600
                PendingChanID:    [32]byte{0, 1, 2, 3},
4✔
601
        }
4✔
602
        if _, err := alice.InitChannelReservation(req); err != nil {
4✔
603
                t.Fatalf("unable to initialize funding reservation 1: %v", err)
×
604
        }
×
605

606
        // Now attempt to reserve funds for another channel, this time
607
        // requesting 900 BTC. We only have around 64BTC worth of outpoints
608
        // that aren't locked, so this should fail.
609
        amt, err := btcutil.NewAmount(900)
4✔
610
        require.NoError(t, err, "unable to create amt")
4✔
611
        failedReq := &lnwallet.InitFundingReserveMsg{
4✔
612
                ChainHash:        chainHash,
4✔
613
                NodeID:           bobPub,
4✔
614
                NodeAddr:         bobAddr,
4✔
615
                LocalFundingAmt:  amt,
4✔
616
                RemoteFundingAmt: 0,
4✔
617
                CommitFeePerKw:   feePerKw,
4✔
618
                FundingFeePerKw:  feePerKw,
4✔
619
                PushMSat:         0,
4✔
620
                Flags:            lnwire.FFAnnounceChannel,
4✔
621
                PendingChanID:    [32]byte{1, 2, 3, 4},
4✔
622
        }
4✔
623
        failedReservation, err := alice.InitChannelReservation(failedReq)
4✔
624
        if err == nil {
4✔
625
                t.Fatalf("not error returned, should fail on coin selection")
×
626
        }
×
627
        if _, ok := err.(*chanfunding.ErrInsufficientFunds); !ok {
4✔
628
                t.Fatalf("error not coinselect error: %v", err)
×
629
        }
×
630
        if failedReservation != nil {
4✔
631
                t.Fatalf("reservation should be nil")
×
632
        }
×
633
}
634

635
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
636
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
637

4✔
638
        feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
4✔
639
        require.NoError(t, err, "unable to query fee estimator")
4✔
640

4✔
641
        // Create a reservation for 44 BTC.
4✔
642
        fundingAmount, err := btcutil.NewAmount(44)
4✔
643
        require.NoError(t, err, "unable to create amt")
4✔
644
        req := &lnwallet.InitFundingReserveMsg{
4✔
645
                ChainHash:        chainHash,
4✔
646
                NodeID:           bobPub,
4✔
647
                NodeAddr:         bobAddr,
4✔
648
                LocalFundingAmt:  fundingAmount,
4✔
649
                RemoteFundingAmt: 0,
4✔
650
                CommitFeePerKw:   feePerKw,
4✔
651
                FundingFeePerKw:  feePerKw,
4✔
652
                PushMSat:         0,
4✔
653
                Flags:            lnwire.FFAnnounceChannel,
4✔
654
                PendingChanID:    [32]byte{2, 3, 4, 5},
4✔
655
        }
4✔
656
        chanReservation, err := alice.InitChannelReservation(req)
4✔
657
        require.NoError(t, err, "unable to initialize funding reservation")
4✔
658

4✔
659
        // Attempt to create another channel with 44 BTC, this should fail.
4✔
660
        req.PendingChanID = [32]byte{3, 4, 5, 6}
4✔
661
        _, err = alice.InitChannelReservation(req)
4✔
662
        if _, ok := err.(*chanfunding.ErrInsufficientFunds); !ok {
4✔
663
                t.Fatalf("coin selection succeeded should have insufficient funds: %v",
×
664
                        err)
×
665
        }
×
666

667
        // Now cancel that old reservation.
668
        if err := chanReservation.Cancel(); err != nil {
4✔
669
                t.Fatalf("unable to cancel reservation: %v", err)
×
670
        }
×
671

672
        // Those outpoints should no longer be locked.
673
        lockedOutPoints := alice.LockedOutpoints()
4✔
674
        if len(lockedOutPoints) != 0 {
4✔
675
                t.Fatalf("outpoints still locked")
×
676
        }
×
677

678
        // Reservation ID should no longer be tracked.
679
        numReservations := alice.ActiveReservations()
4✔
680
        if len(alice.ActiveReservations()) != 0 {
4✔
681
                t.Fatalf("should have 0 reservations, instead have %v",
×
682
                        numReservations)
×
683
        }
×
684

685
        // TODO(roasbeef): create method like Balance that ignores locked
686
        // outpoints, will let us fail early/fast instead of querying and
687
        // attempting coin selection.
688

689
        // Request to fund a new channel should now succeed.
690
        req.PendingChanID = [32]byte{4, 5, 6, 7, 8}
4✔
691
        if _, err := alice.InitChannelReservation(req); err != nil {
4✔
692
                t.Fatalf("unable to initialize funding reservation: %v", err)
×
693
        }
×
694
}
695

696
func testCancelNonExistentReservation(miner *rpctest.Harness,
697
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
698

4✔
699
        feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
4✔
700
        require.NoError(t, err, "unable to query fee estimator")
4✔
701

4✔
702
        req := &lnwallet.InitFundingReserveMsg{
4✔
703
                CommitFeePerKw: feePerKw,
4✔
704
                PushMSat:       10,
4✔
705
                Flags:          lnwire.FFAnnounceChannel,
4✔
706
                CommitType:     lnwallet.CommitmentTypeTweakless,
4✔
707
                PendingChanID:  [32]byte{},
4✔
708
        }
4✔
709

4✔
710
        // Create our own reservation, give it some ID.
4✔
711
        res, err := lnwallet.NewChannelReservation(
4✔
712
                10000, 10000, alice, 22, &testHdSeed, 0, req,
4✔
713
        )
4✔
714
        require.NoError(t, err, "unable to create res")
4✔
715

4✔
716
        // Attempt to cancel this reservation. This should fail, we know
4✔
717
        // nothing of it.
4✔
718
        if err := res.Cancel(); err == nil {
4✔
719
                t.Fatalf("canceled non-existent reservation")
×
720
        }
×
721
}
722

723
func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
724
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
725

4✔
726
        // We'll attempt to create a new reservation with an extremely high
4✔
727
        // commitment fee rate. This should push our balance into the negative
4✔
728
        // and result in a failure to create the reservation.
4✔
729
        const numBTC = 4
4✔
730
        fundingAmount, err := btcutil.NewAmount(numBTC)
4✔
731
        require.NoError(t, err, "unable to create amt")
4✔
732

4✔
733
        feePerKw := chainfee.SatPerKWeight(
4✔
734
                numBTC * numBTC * btcutil.SatoshiPerBitcoin,
4✔
735
        )
4✔
736
        req := &lnwallet.InitFundingReserveMsg{
4✔
737
                ChainHash:        chainHash,
4✔
738
                NodeID:           bobPub,
4✔
739
                NodeAddr:         bobAddr,
4✔
740
                LocalFundingAmt:  fundingAmount,
4✔
741
                RemoteFundingAmt: 0,
4✔
742
                CommitFeePerKw:   feePerKw,
4✔
743
                FundingFeePerKw:  1000,
4✔
744
                PushMSat:         0,
4✔
745
                Flags:            lnwire.FFAnnounceChannel,
4✔
746
                CommitType:       lnwallet.CommitmentTypeTweakless,
4✔
747
                PendingChanID:    [32]byte{1},
4✔
748
        }
4✔
749
        _, err = alice.InitChannelReservation(req)
4✔
750
        switch {
4✔
751
        case err == nil:
×
752
                t.Fatalf("initialization should have failed due to " +
×
753
                        "insufficient local amount")
×
754

755
        case !strings.Contains(err.Error(), "funder balance too small"):
×
756
                t.Fatalf("incorrect error: %v", err)
×
757
        }
758
}
759

760
func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContribution) {
64✔
761
        _, _, line, _ := runtime.Caller(1)
64✔
762

64✔
763
        if c.FirstCommitmentPoint == nil {
64✔
764
                t.Fatalf("line #%v: commitment point not fond", line)
×
765
        }
×
766

767
        if c.CsvDelay == 0 {
64✔
768
                t.Fatalf("line #%v: csv delay not set", line)
×
769
        }
×
770

771
        if c.MultiSigKey.PubKey == nil {
64✔
772
                t.Fatalf("line #%v: multi-sig key not set", line)
×
773
        }
×
774
        if c.RevocationBasePoint.PubKey == nil {
64✔
775
                t.Fatalf("line #%v: revocation key not set", line)
×
776
        }
×
777
        if c.PaymentBasePoint.PubKey == nil {
64✔
778
                t.Fatalf("line #%v: payment key not set", line)
×
779
        }
×
780
        if c.DelayBasePoint.PubKey == nil {
64✔
781
                t.Fatalf("line #%v: delay key not set", line)
×
782
        }
×
783

784
        if c.DustLimit == 0 {
64✔
785
                t.Fatalf("line #%v: dust limit not set", line)
×
786
        }
×
787
        if c.MaxPendingAmount == 0 {
64✔
788
                t.Fatalf("line #%v: max pending amt not set", line)
×
789
        }
×
790
        if c.ChanReserve == 0 {
64✔
791
                t.Fatalf("line #%v: chan reserve not set", line)
×
792
        }
×
793
        if c.MinHTLC == 0 {
64✔
794
                t.Fatalf("line #%v: min htlc not set", line)
×
795
        }
×
796
        if c.MaxAcceptedHtlcs == 0 {
64✔
797
                t.Fatalf("line #%v: max accepted htlc's not set", line)
×
798
        }
×
799
}
800

801
func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
802
        alice, bob *lnwallet.LightningWallet, t *testing.T,
803
        commitType lnwallet.CommitmentType,
804
        aliceChanFunder chanfunding.Assembler, fetchFundingTx func() *wire.MsgTx,
805
        pendingChanID [32]byte, thawHeight uint32) {
16✔
806

16✔
807
        // For this scenario, Alice will be the channel initiator while bob
16✔
808
        // will act as the responder to the workflow.
16✔
809

16✔
810
        // First, Alice will Initialize a reservation for a channel with 4 BTC
16✔
811
        // funded solely by us. We'll also initially push 1 BTC of the channel
16✔
812
        // towards Bob's side.
16✔
813
        fundingAmt, err := btcutil.NewAmount(4)
16✔
814
        require.NoError(t, err, "unable to create amt")
16✔
815
        pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
16✔
816
        feePerKw, err := alice.Cfg.FeeEstimator.EstimateFeePerKW(1)
16✔
817
        require.NoError(t, err, "unable to query fee estimator")
16✔
818
        aliceReq := &lnwallet.InitFundingReserveMsg{
16✔
819
                ChainHash:        chainHash,
16✔
820
                PendingChanID:    pendingChanID,
16✔
821
                NodeID:           bobPub,
16✔
822
                NodeAddr:         bobAddr,
16✔
823
                LocalFundingAmt:  fundingAmt,
16✔
824
                RemoteFundingAmt: 0,
16✔
825
                CommitFeePerKw:   feePerKw,
16✔
826
                FundingFeePerKw:  feePerKw,
16✔
827
                PushMSat:         pushAmt,
16✔
828
                Flags:            lnwire.FFAnnounceChannel,
16✔
829
                CommitType:       commitType,
16✔
830
                ChanFunder:       aliceChanFunder,
16✔
831
        }
16✔
832
        aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
16✔
833
        require.NoError(t, err, "unable to init channel reservation")
16✔
834
        aliceChanReservation.SetNumConfsRequired(numReqConfs)
16✔
835
        bounds := &channeldb.ChannelStateBounds{
16✔
836
                ChanReserve:      fundingAmt / 100,
16✔
837
                MaxPendingAmount: lnwire.NewMSatFromSatoshis(fundingAmt),
16✔
838
                MinHTLC:          1,
16✔
839
                MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
16✔
840
        }
16✔
841
        commitParams := &channeldb.CommitmentParams{
16✔
842
                DustLimit: lnwallet.DustLimitUnknownWitness(),
16✔
843
                CsvDelay:  csvDelay,
16✔
844
        }
16✔
845
        err = aliceChanReservation.CommitConstraints(
16✔
846
                bounds, commitParams, defaultMaxLocalCsvDelay, false,
16✔
847
        )
16✔
848
        require.NoError(t, err, "unable to verify constraints")
16✔
849

16✔
850
        // Verify all contribution fields have been set properly, but only if
16✔
851
        // Alice is the funder herself.
16✔
852
        aliceContribution := aliceChanReservation.OurContribution()
16✔
853
        if fetchFundingTx == nil {
28✔
854
                if len(aliceContribution.Inputs) < 1 {
12✔
855
                        t.Fatalf("outputs for funding tx not properly "+
×
856
                                "selected, have %v outputs should at least 1",
×
857
                                len(aliceContribution.Inputs))
×
858
                }
×
859
                if len(aliceContribution.ChangeOutputs) != 1 {
12✔
860
                        t.Fatalf("coin selection failed, should have one "+
×
861
                                "change outputs, instead have: %v",
×
862
                                len(aliceContribution.ChangeOutputs))
×
863
                }
×
864
        }
865
        assertContributionInitPopulated(t, aliceContribution)
16✔
866

16✔
867
        // Next, Bob receives the initial request, generates a corresponding
16✔
868
        // reservation initiation, then consume Alice's contribution.
16✔
869
        bobReq := &lnwallet.InitFundingReserveMsg{
16✔
870
                ChainHash:        chainHash,
16✔
871
                PendingChanID:    pendingChanID,
16✔
872
                NodeID:           alicePub,
16✔
873
                NodeAddr:         aliceAddr,
16✔
874
                LocalFundingAmt:  0,
16✔
875
                RemoteFundingAmt: fundingAmt,
16✔
876
                CommitFeePerKw:   feePerKw,
16✔
877
                FundingFeePerKw:  feePerKw,
16✔
878
                PushMSat:         pushAmt,
16✔
879
                Flags:            lnwire.FFAnnounceChannel,
16✔
880
                CommitType:       commitType,
16✔
881
        }
16✔
882
        bobChanReservation, err := bob.InitChannelReservation(bobReq)
16✔
883
        require.NoError(t, err, "unable to create bob reservation")
16✔
884
        err = bobChanReservation.CommitConstraints(
16✔
885
                bounds, commitParams, defaultMaxLocalCsvDelay, true,
16✔
886
        )
16✔
887
        require.NoError(t, err, "unable to verify constraints")
16✔
888
        bobChanReservation.SetNumConfsRequired(numReqConfs)
16✔
889

16✔
890
        // We'll ensure that Bob's contribution also gets generated properly.
16✔
891
        bobContribution := bobChanReservation.OurContribution()
16✔
892
        assertContributionInitPopulated(t, bobContribution)
16✔
893

16✔
894
        // With his contribution generated, he can now process Alice's
16✔
895
        // contribution.
16✔
896
        err = bobChanReservation.ProcessSingleContribution(aliceContribution)
16✔
897
        require.NoError(t, err, "bob unable to process alice's contribution")
16✔
898
        assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
16✔
899

16✔
900
        // Bob will next send over his contribution to Alice, we simulate this
16✔
901
        // by having Alice immediately process his contribution.
16✔
902
        err = aliceChanReservation.ProcessContribution(bobContribution)
16✔
903
        if err != nil {
16✔
904
                t.Fatalf("alice unable to process bob's contribution: %v", err)
×
905
        }
×
906
        assertContributionInitPopulated(t, bobChanReservation.TheirContribution())
16✔
907

16✔
908
        // At this point, Alice should have generated all the signatures
16✔
909
        // required for the funding transaction, as well as Alice's commitment
16✔
910
        // signature to bob, but only if the funding transaction was
16✔
911
        // constructed internally.
16✔
912
        aliceRemoteContribution := aliceChanReservation.TheirContribution()
16✔
913
        aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
16✔
914
        if fetchFundingTx == nil && aliceFundingSigs == nil {
16✔
915
                t.Fatalf("funding sigs not found")
×
916
        }
×
917
        if aliceCommitSig == nil {
16✔
918
                t.Fatalf("commitment sig not found")
×
919
        }
×
920

921
        // Additionally, the funding tx and the funding outpoint should have
922
        // been populated.
923
        if aliceChanReservation.FinalFundingTx() == nil && fetchFundingTx == nil {
16✔
924
                t.Fatalf("funding transaction never created!")
×
925
        }
×
926
        if aliceChanReservation.FundingOutpoint() == nil {
16✔
927
                t.Fatalf("funding outpoint never created!")
×
928
        }
×
929

930
        // Their funds should also be filled in.
931
        if len(aliceRemoteContribution.Inputs) != 0 {
16✔
932
                t.Fatalf("bob shouldn't have any inputs, instead has %v",
×
933
                        len(aliceRemoteContribution.Inputs))
×
934
        }
×
935
        if len(aliceRemoteContribution.ChangeOutputs) != 0 {
16✔
936
                t.Fatalf("bob shouldn't have any change outputs, instead "+
×
937
                        "has %v",
×
938
                        aliceRemoteContribution.ChangeOutputs[0].Value)
×
939
        }
×
940

941
        // Next, Alice will send over her signature for Bob's commitment
942
        // transaction, as well as the funding outpoint.
943
        fundingPoint := aliceChanReservation.FundingOutpoint()
16✔
944
        _, err = bobChanReservation.CompleteReservationSingle(
16✔
945
                fundingPoint, aliceCommitSig,
16✔
946
                fn.None[lnwallet.AuxFundingDesc](),
16✔
947
        )
16✔
948
        require.NoError(t, err, "bob unable to consume single reservation")
16✔
949

16✔
950
        // Finally, we'll conclude the reservation process by sending over
16✔
951
        // Bob's commitment signature, which is the final thing Alice needs to
16✔
952
        // be able to safely broadcast the funding transaction.
16✔
953
        _, bobCommitSig := bobChanReservation.OurSignatures()
16✔
954
        if bobCommitSig == nil {
16✔
955
                t.Fatalf("bob failed to generate commitment signature: %v", err)
×
956
        }
×
957
        _, err = aliceChanReservation.CompleteReservation(
16✔
958
                nil, bobCommitSig,
16✔
959
        )
16✔
960
        require.NoError(t, err, "alice unable to complete reservation")
16✔
961

16✔
962
        // If the caller provided an alternative way to obtain the funding tx,
16✔
963
        // then we'll use that. Otherwise, we'll obtain it directly from Alice.
16✔
964
        var fundingTx *wire.MsgTx
16✔
965
        if fetchFundingTx != nil {
20✔
966
                fundingTx = fetchFundingTx()
4✔
967
        } else {
16✔
968
                fundingTx = aliceChanReservation.FinalFundingTx()
12✔
969
        }
12✔
970

971
        // The resulting active channel state should have been persisted to the
972
        // DB for both Alice and Bob.
973
        fundingSha := fundingTx.TxHash()
16✔
974
        aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
16✔
975
        require.NoError(t, err, "unable to retrieve channel from DB")
16✔
976
        if len(aliceChannels) != 1 {
16✔
977
                t.Fatalf("alice didn't save channel state: %v", err)
×
978
        }
×
979
        if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
16✔
980
                t.Fatalf("channel state not properly saved: %v vs %v",
×
981
                        hex.EncodeToString(aliceChannels[0].FundingOutpoint.Hash[:]),
×
982
                        hex.EncodeToString(fundingSha[:]))
×
983
        }
×
984
        if !aliceChannels[0].IsInitiator {
16✔
985
                t.Fatalf("alice not detected as channel initiator")
×
986
        }
×
987
        if !aliceChannels[0].ChanType.IsSingleFunder() {
16✔
988
                t.Fatalf("channel type is incorrect, expected %v instead got %v",
×
989
                        channeldb.SingleFunderBit, aliceChannels[0].ChanType)
×
990
        }
×
991

992
        bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
16✔
993
        require.NoError(t, err, "unable to retrieve channel from DB")
16✔
994
        if len(bobChannels) != 1 {
16✔
995
                t.Fatalf("bob didn't save channel state: %v", err)
×
996
        }
×
997
        if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
16✔
998
                t.Fatalf("channel state not properly saved: %v vs %v",
×
999
                        hex.EncodeToString(bobChannels[0].FundingOutpoint.Hash[:]),
×
1000
                        hex.EncodeToString(fundingSha[:]))
×
1001
        }
×
1002
        if bobChannels[0].IsInitiator {
16✔
1003
                t.Fatalf("bob not detected as channel responder")
×
1004
        }
×
1005
        if !bobChannels[0].ChanType.IsSingleFunder() {
16✔
1006
                t.Fatalf("channel type is incorrect, expected %v instead got %v",
×
1007
                        channeldb.SingleFunderBit, bobChannels[0].ChanType)
×
1008
        }
×
1009

1010
        // Let Alice publish the funding transaction.
1011
        err = alice.PublishTransaction(fundingTx, "")
16✔
1012
        require.NoError(t, err, "unable to publish funding tx")
16✔
1013

16✔
1014
        // Mine a single block, the funding transaction should be included
16✔
1015
        // within this block.
16✔
1016
        err = waitForMempoolTx(miner, &fundingSha)
16✔
1017
        require.NoError(t, err, "tx not relayed to miner")
16✔
1018
        blockHashes, err := miner.Client.Generate(1)
16✔
1019
        require.NoError(t, err, "unable to generate block")
16✔
1020
        block, err := miner.Client.GetBlock(blockHashes[0])
16✔
1021
        require.NoError(t, err, "unable to find block")
16✔
1022
        if len(block.Transactions) != 2 {
16✔
1023
                t.Fatalf("funding transaction wasn't mined: %d",
×
1024
                        len(block.Transactions))
×
1025
        }
×
1026
        blockTx := block.Transactions[1]
16✔
1027
        if blockTx.TxHash() != fundingSha {
16✔
1028
                t.Fatalf("incorrect transaction was mined")
×
1029
        }
×
1030

1031
        // If a frozen channel was requested, then we expect that both channel
1032
        // types show as being a frozen channel type.
1033
        aliceChanFrozen := aliceChannels[0].ChanType.IsFrozen()
16✔
1034
        bobChanFrozen := bobChannels[0].ChanType.IsFrozen()
16✔
1035
        if thawHeight != 0 && (!aliceChanFrozen || !bobChanFrozen) {
16✔
1036
                t.Fatalf("expected both alice and bob to have frozen chans: "+
×
1037
                        "alice_frozen=%v, bob_frozen=%v", aliceChanFrozen,
×
1038
                        bobChanFrozen)
×
1039
        }
×
1040
        if thawHeight != bobChannels[0].ThawHeight {
16✔
1041
                t.Fatalf("wrong thaw height: expected %v got %v", thawHeight,
×
1042
                        bobChannels[0].ThawHeight)
×
1043
        }
×
1044
        if thawHeight != aliceChannels[0].ThawHeight {
16✔
1045
                t.Fatalf("wrong thaw height: expected %v got %v", thawHeight,
×
1046
                        aliceChannels[0].ThawHeight)
×
1047
        }
×
1048

1049
        assertReservationDeleted(aliceChanReservation, t)
16✔
1050
        assertReservationDeleted(bobChanReservation, t)
16✔
1051
}
1052

1053
func testListTransactionDetails(miner *rpctest.Harness,
1054
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1055

4✔
1056
        // Create 5 new outputs spendable by the wallet.
4✔
1057
        const numTxns = 5
4✔
1058
        const outputAmt = btcutil.SatoshiPerBitcoin
4✔
1059
        isOurAddress := make(map[string]bool)
4✔
1060
        txids := make(map[chainhash.Hash]struct{})
4✔
1061
        for i := 0; i < numTxns; i++ {
24✔
1062
                addr, err := alice.NewAddress(
20✔
1063
                        lnwallet.WitnessPubKey, false,
20✔
1064
                        lnwallet.DefaultAccountName,
20✔
1065
                )
20✔
1066
                if err != nil {
20✔
1067
                        t.Fatalf("unable to create new address: %v", err)
×
1068
                }
×
1069
                isOurAddress[addr.EncodeAddress()] = true
20✔
1070
                script, err := txscript.PayToAddrScript(addr)
20✔
1071
                if err != nil {
20✔
1072
                        t.Fatalf("unable to create output script: %v", err)
×
1073
                }
×
1074

1075
                output := &wire.TxOut{
20✔
1076
                        Value:    outputAmt,
20✔
1077
                        PkScript: script,
20✔
1078
                }
20✔
1079
                txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
20✔
1080
                if err != nil {
20✔
1081
                        t.Fatalf("unable to send coinbase: %v", err)
×
1082
                }
×
1083
                txids[*txid] = struct{}{}
20✔
1084
        }
1085

1086
        // Get the miner's current best block height before we mine blocks.
1087
        _, startHeight, err := miner.Client.GetBestBlock()
4✔
1088
        require.NoError(t, err, "cannot get best block")
4✔
1089

4✔
1090
        // Generate 10 blocks to mine all the transactions created above.
4✔
1091
        const numBlocksMined = 10
4✔
1092
        blocks, err := miner.Client.Generate(numBlocksMined)
4✔
1093
        require.NoError(t, err, "unable to mine blocks")
4✔
1094

4✔
1095
        // Our new best block height should be our start height + the number of
4✔
1096
        // blocks we just mined.
4✔
1097
        chainTip := startHeight + numBlocksMined
4✔
1098

4✔
1099
        // Next, fetch all the current transaction details. We should find all
4✔
1100
        // of our transactions between our start height before we generated
4✔
1101
        // blocks, and our end height, which is the chain tip. This query does
4✔
1102
        // not include unconfirmed transactions, since all of our transactions
4✔
1103
        // should be confirmed.
4✔
1104
        err = waitForWalletSync(miner, alice)
4✔
1105
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1106
        txDetails, _, _, err := alice.ListTransactionDetails(
4✔
1107
                startHeight, chainTip, "", 0, 1000,
4✔
1108
        )
4✔
1109
        require.NoError(t, err, "unable to fetch tx details")
4✔
1110

4✔
1111
        // This is a mapping from:
4✔
1112
        // blockHash -> transactionHash -> transactionOutputs
4✔
1113
        blockTxOuts := make(map[chainhash.Hash]map[chainhash.Hash][]*wire.TxOut)
4✔
1114

4✔
1115
        // Each of the transactions created above should be found with the
4✔
1116
        // proper details populated.
4✔
1117
        for _, txDetail := range txDetails {
40✔
1118
                if _, ok := txids[txDetail.Hash]; !ok {
52✔
1119
                        continue
16✔
1120
                }
1121

1122
                if txDetail.NumConfirmations != numBlocksMined {
20✔
1123
                        t.Fatalf("num confs incorrect, got %v expected %v",
×
1124
                                txDetail.NumConfirmations, numBlocksMined)
×
1125
                }
×
1126
                if txDetail.Value != outputAmt {
20✔
1127
                        t.Fatalf("tx value incorrect, got %v expected %v",
×
1128
                                txDetail.Value, outputAmt)
×
1129
                }
×
1130

1131
                if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) {
20✔
1132
                        t.Fatalf("block hash mismatch, got %v expected %v",
×
1133
                                txDetail.BlockHash, blocks[0])
×
1134
                }
×
1135

1136
                // This fetches the transactions in a block so that we can compare the
1137
                // txouts stored in the mined transaction against the ones in the transaction
1138
                // details
1139
                if _, ok := blockTxOuts[*txDetail.BlockHash]; !ok {
24✔
1140
                        fetchedBlock, err := alice.Cfg.ChainIO.GetBlock(txDetail.BlockHash)
4✔
1141
                        if err != nil {
4✔
1142
                                t.Fatalf("err fetching block: %s", err)
×
1143
                        }
×
1144

1145
                        transactions := make(
4✔
1146
                                map[chainhash.Hash][]*wire.TxOut,
4✔
1147
                                len(fetchedBlock.Transactions),
4✔
1148
                        )
4✔
1149
                        for _, tx := range fetchedBlock.Transactions {
32✔
1150
                                transactions[tx.TxHash()] = tx.Copy().TxOut
28✔
1151
                        }
28✔
1152

1153
                        blockTxOuts[fetchedBlock.BlockHash()] = transactions
4✔
1154
                }
1155

1156
                if txOuts, ok := blockTxOuts[*txDetail.BlockHash][txDetail.Hash]; !ok {
20✔
1157
                        t.Fatalf("tx (%v) not found in block (%v)",
×
1158
                                txDetail.Hash, txDetail.BlockHash)
×
1159
                } else {
20✔
1160
                        var destinationOutputs []lnwallet.OutputDetail
20✔
1161

20✔
1162
                        for i, txOut := range txOuts {
60✔
1163
                                sc, addrs, _, err :=
40✔
1164
                                        txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams)
40✔
1165
                                if err != nil {
40✔
1166
                                        t.Fatalf("err extract script addresses: %s", err)
×
1167
                                }
×
1168
                                destinationOutputs = append(destinationOutputs, lnwallet.OutputDetail{
40✔
1169
                                        OutputType:   sc,
40✔
1170
                                        Addresses:    addrs,
40✔
1171
                                        PkScript:     txOut.PkScript,
40✔
1172
                                        OutputIndex:  i,
40✔
1173
                                        Value:        btcutil.Amount(txOut.Value),
40✔
1174
                                        IsOurAddress: isOurAddress[addrs[0].EncodeAddress()],
40✔
1175
                                })
40✔
1176
                        }
1177

1178
                        if !reflect.DeepEqual(txDetail.OutputDetails, destinationOutputs) {
20✔
1179
                                t.Fatalf("destination outputs mismatch, got %v expected %v",
×
1180
                                        txDetail.OutputDetails, destinationOutputs)
×
1181
                        }
×
1182
                }
1183

1184
                delete(txids, txDetail.Hash)
20✔
1185
        }
1186
        if len(txids) != 0 {
4✔
1187
                t.Fatalf("all transactions not found in details: left=%v, "+
×
1188
                        "returned_set=%v", spew.Sdump(txids),
×
1189
                        spew.Sdump(txDetails))
×
1190
        }
×
1191

1192
        // Next create a transaction paying to an output which isn't under the
1193
        // wallet's control.
1194
        minerAddr, err := miner.NewAddress()
4✔
1195
        require.NoError(t, err, "unable to generate address")
4✔
1196
        outputScript, err := txscript.PayToAddrScript(minerAddr)
4✔
1197
        require.NoError(t, err, "unable to make output script")
4✔
1198
        burnOutput := wire.NewTxOut(outputAmt, outputScript)
4✔
1199
        burnTX, err := alice.SendOutputs(
4✔
1200
                nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
4✔
1201
                alice.Cfg.CoinSelectionStrategy,
4✔
1202
        )
4✔
1203
        require.NoError(t, err, "unable to create burn tx")
4✔
1204
        burnTXID := burnTX.TxHash()
4✔
1205
        err = waitForMempoolTx(miner, &burnTXID)
4✔
1206
        require.NoError(t, err, "tx not relayed to miner")
4✔
1207

4✔
1208
        // Before we mine the next block, we'll ensure that the above
4✔
1209
        // transaction shows up in the set of unconfirmed transactions returned
4✔
1210
        // by ListTransactionDetails.
4✔
1211
        err = waitForWalletSync(miner, alice)
4✔
1212
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1213

4✔
1214
        // Query our wallet for transactions from the chain tip, including
4✔
1215
        // unconfirmed transactions. The transaction above should be included
4✔
1216
        // with a confirmation height of 0, indicating that it has not been
4✔
1217
        // mined yet.
4✔
1218
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1219
                chainTip, btcwallet.UnconfirmedHeight, "", 0, 1000,
4✔
1220
        )
4✔
1221
        require.NoError(t, err, "unable to fetch tx details")
4✔
1222
        var mempoolTxFound bool
4✔
1223
        for _, txDetail := range txDetails {
8✔
1224
                if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) {
4✔
1225
                        continue
×
1226
                }
1227

1228
                // Now that we've found the transaction, ensure that it has a
1229
                // negative number of confirmations to indicate that it's
1230
                // unconfirmed.
1231
                mempoolTxFound = true
4✔
1232
                if txDetail.NumConfirmations != 0 {
4✔
1233
                        t.Fatalf("num confs incorrect, got %v expected %v",
×
1234
                                txDetail.NumConfirmations, 0)
×
1235
                }
×
1236

1237
                // We test that each txDetail has destination addresses. This ensures
1238
                // that even when we have 0 confirmation transactions, the destination
1239
                // addresses are returned.
1240
                var match bool
4✔
1241
                for _, o := range txDetail.OutputDetails {
12✔
1242
                        for _, addr := range o.Addresses {
16✔
1243
                                if addr.String() == minerAddr.String() {
12✔
1244
                                        match = true
4✔
1245
                                        break
4✔
1246
                                }
1247
                        }
1248
                }
1249
                if !match {
4✔
1250
                        t.Fatalf("minerAddr: %v should have been a dest addr", minerAddr)
×
1251
                }
×
1252
        }
1253
        if !mempoolTxFound {
4✔
1254
                t.Fatalf("unable to find mempool tx in tx details!")
×
1255
        }
×
1256

1257
        // Generate one block for our transaction to confirm in.
1258
        var numBlocks int32 = 1
4✔
1259
        burnBlock, err := miner.Client.Generate(uint32(numBlocks))
4✔
1260
        require.NoError(t, err, "unable to mine block")
4✔
1261

4✔
1262
        // Progress our chain tip by the number of blocks we have just mined.
4✔
1263
        chainTip += numBlocks
4✔
1264

4✔
1265
        // Fetch the transaction details again, the new transaction should be
4✔
1266
        // shown as debiting from the wallet's balance. Start and end height
4✔
1267
        // are inclusive, so we use chainTip for both parameters to get only
4✔
1268
        // transactions from the last block.
4✔
1269
        err = waitForWalletSync(miner, alice)
4✔
1270
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1271
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1272
                chainTip, chainTip, "", 0, 1000,
4✔
1273
        )
4✔
1274
        require.NoError(t, err, "unable to fetch tx details")
4✔
1275
        var burnTxFound bool
4✔
1276
        for _, txDetail := range txDetails {
8✔
1277
                if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) {
4✔
1278
                        continue
×
1279
                }
1280

1281
                burnTxFound = true
4✔
1282
                if txDetail.NumConfirmations != 1 {
4✔
1283
                        t.Fatalf("num confs incorrect, got %v expected %v",
×
1284
                                txDetail.NumConfirmations, 1)
×
1285
                }
×
1286

1287
                // We assert that the value is greater than the amount we
1288
                // attempted to send, as the wallet should have paid some amount
1289
                // of network fees.
1290
                if txDetail.Value >= -outputAmt {
4✔
1291
                        fmt.Println(spew.Sdump(txDetail))
×
1292
                        t.Fatalf("tx value incorrect, got %v expected %v",
×
1293
                                int64(txDetail.Value), -int64(outputAmt))
×
1294
                }
×
1295
                if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) {
4✔
1296
                        t.Fatalf("block hash mismatch, got %v expected %v",
×
1297
                                txDetail.BlockHash, burnBlock[0])
×
1298
                }
×
1299
        }
1300
        if !burnTxFound {
4✔
1301
                t.Fatal("tx burning btc not found")
×
1302
        }
×
1303

1304
        // Generate a block which has no wallet transactions in it.
1305
        chainTip += numBlocks
4✔
1306
        _, err = miner.Client.Generate(uint32(numBlocks))
4✔
1307
        require.NoError(t, err, "unable to mine block")
4✔
1308

4✔
1309
        err = waitForWalletSync(miner, alice)
4✔
1310
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1311

4✔
1312
        // Query for transactions only in the latest block. We do not expect
4✔
1313
        // any transactions to be returned.
4✔
1314
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1315
                chainTip, chainTip, "", 0, 1000,
4✔
1316
        )
4✔
1317
        require.NoError(t, err, "unexpected error")
4✔
1318
        if len(txDetails) != 0 {
4✔
1319
                t.Fatalf("expected 0 transactions, got: %v", len(txDetails))
×
1320
        }
×
1321
}
1322

1323
func testListTransactionDetailsOffset(miner *rpctest.Harness,
1324
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1325

4✔
1326
        // Create 5 new outputs spendable by the wallet.
4✔
1327
        const numTxns = 5
4✔
1328
        const outputAmt = btcutil.SatoshiPerBitcoin
4✔
1329
        isOurAddress := make(map[string]bool)
4✔
1330
        txids := make(map[chainhash.Hash]struct{})
4✔
1331
        for i := 0; i < numTxns; i++ {
24✔
1332
                addr, err := alice.NewAddress(
20✔
1333
                        lnwallet.WitnessPubKey, false,
20✔
1334
                        lnwallet.DefaultAccountName,
20✔
1335
                )
20✔
1336
                require.NoError(t, err)
20✔
1337

20✔
1338
                isOurAddress[addr.EncodeAddress()] = true
20✔
1339
                script, err := txscript.PayToAddrScript(addr)
20✔
1340
                require.NoError(t, err)
20✔
1341

20✔
1342
                output := &wire.TxOut{
20✔
1343
                        Value:    outputAmt,
20✔
1344
                        PkScript: script,
20✔
1345
                }
20✔
1346
                txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
20✔
1347
                require.NoError(t, err)
20✔
1348
                txids[*txid] = struct{}{}
20✔
1349
        }
20✔
1350

1351
        // Get the miner's current best block height before we mine blocks.
1352
        _, startHeight, err := miner.Client.GetBestBlock()
4✔
1353
        require.NoError(t, err, "cannot get best block")
4✔
1354

4✔
1355
        // Generate 10 blocks to mine all the transactions created above.
4✔
1356
        const numBlocksMined = 10
4✔
1357
        _, err = miner.Client.Generate(numBlocksMined)
4✔
1358
        require.NoError(t, err, "unable to mine blocks")
4✔
1359

4✔
1360
        // Our new best block height should be our start height + the number of
4✔
1361
        // blocks we just mined.
4✔
1362
        chainTip := startHeight + numBlocksMined
4✔
1363

4✔
1364
        err = waitForWalletSync(miner, alice)
4✔
1365
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1366

4✔
1367
        // Query for transactions, setting max_transactions to 5. We expect 5
4✔
1368
        // transactions to be returned.
4✔
1369
        txDetails, firstIdx, lastIdx, err := alice.ListTransactionDetails(
4✔
1370
                startHeight, chainTip, "", 0, 5,
4✔
1371
        )
4✔
1372
        require.NoError(t, err)
4✔
1373
        require.Len(t, txDetails, 5)
4✔
1374
        require.EqualValues(t, 0, firstIdx)
4✔
1375
        require.EqualValues(t, 4, lastIdx)
4✔
1376

4✔
1377
        // Query for transactions, setting max_transactions to less than the
4✔
1378
        // number of transactions we have (5).
4✔
1379
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1380
                startHeight, chainTip, "", 0, 1,
4✔
1381
        )
4✔
1382
        require.NoError(t, err)
4✔
1383
        require.Len(t, txDetails, 1)
4✔
1384

4✔
1385
        // Query for transactions, setting indexOffset to 5 (equal to number
4✔
1386
        // of transactions we have) and max_transactions to 0.
4✔
1387
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1388
                startHeight, chainTip, "", 5, 0,
4✔
1389
        )
4✔
1390
        require.NoError(t, err)
4✔
1391
        require.Len(t, txDetails, 0)
4✔
1392

4✔
1393
        // Query for transactions, setting indexOffset to 4 (edge offset) and
4✔
1394
        // max_transactions to 0.
4✔
1395
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1396
                startHeight, chainTip, "", 4, 0,
4✔
1397
        )
4✔
1398
        require.NoError(t, err)
4✔
1399
        require.Len(t, txDetails, 1)
4✔
1400

4✔
1401
        // Query for transactions, setting max_transactions to 0.
4✔
1402
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1403
                startHeight, chainTip, "", 0, 0,
4✔
1404
        )
4✔
1405
        require.NoError(t, err)
4✔
1406
        require.Len(t, txDetails, 5)
4✔
1407

4✔
1408
        // Query for transactions, more than we have in the wallet (5).
4✔
1409
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1410
                startHeight, chainTip, "", 0, 10,
4✔
1411
        )
4✔
1412
        require.NoError(t, err)
4✔
1413
        require.Len(t, txDetails, 5)
4✔
1414

4✔
1415
        // Query for transactions where the offset is greater than the number
4✔
1416
        // of transactions available.
4✔
1417
        txDetails, _, _, err = alice.ListTransactionDetails(
4✔
1418
                startHeight, chainTip, "", 10, 100,
4✔
1419
        )
4✔
1420
        require.NoError(t, err)
4✔
1421
        require.Len(t, txDetails, 0)
4✔
1422
}
1423

1424
func testTransactionSubscriptions(miner *rpctest.Harness,
1425
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1426

4✔
1427
        // First, check to see if this wallet meets the TransactionNotifier
4✔
1428
        // interface, if not then we'll skip this test for this particular
4✔
1429
        // implementation of the WalletController.
4✔
1430
        txClient, err := alice.SubscribeTransactions()
4✔
1431
        if err != nil {
4✔
1432
                t.Skipf("unable to generate tx subscription: %v", err)
×
1433
        }
×
1434
        defer txClient.Cancel()
4✔
1435

4✔
1436
        const (
4✔
1437
                outputAmt = btcutil.SatoshiPerBitcoin
4✔
1438
                numTxns   = 3
4✔
1439
        )
4✔
1440
        errCh1 := make(chan error, 1)
4✔
1441
        switch alice.BackEnd() {
4✔
1442
        case "neutrino":
1✔
1443
                // Neutrino doesn't listen for unconfirmed transactions.
1444
        default:
3✔
1445
                go func() {
6✔
1446
                        for i := 0; i < numTxns; i++ {
12✔
1447
                                txDetail := <-txClient.UnconfirmedTransactions()
9✔
1448
                                if txDetail.NumConfirmations != 0 {
9✔
1449
                                        errCh1 <- fmt.Errorf("incorrect number of confs, "+
×
1450
                                                "expected %v got %v", 0,
×
1451
                                                txDetail.NumConfirmations)
×
1452
                                        return
×
1453
                                }
×
1454
                                if txDetail.Value != outputAmt {
9✔
1455
                                        errCh1 <- fmt.Errorf("incorrect output amt, "+
×
1456
                                                "expected %v got %v", outputAmt,
×
1457
                                                txDetail.Value)
×
1458
                                        return
×
1459
                                }
×
1460
                                if txDetail.BlockHash != nil {
9✔
1461
                                        errCh1 <- fmt.Errorf("block hash should be nil, "+
×
1462
                                                "is instead %v",
×
1463
                                                txDetail.BlockHash)
×
1464
                                        return
×
1465
                                }
×
1466
                        }
1467
                        errCh1 <- nil
3✔
1468
                }()
1469
        }
1470

1471
        // Next, fetch a fresh address from the wallet, create 3 new outputs
1472
        // with the pkScript.
1473
        for i := 0; i < numTxns; i++ {
16✔
1474
                addr, err := alice.NewAddress(
12✔
1475
                        lnwallet.WitnessPubKey, false,
12✔
1476
                        lnwallet.DefaultAccountName,
12✔
1477
                )
12✔
1478
                if err != nil {
12✔
1479
                        t.Fatalf("unable to create new address: %v", err)
×
1480
                }
×
1481
                script, err := txscript.PayToAddrScript(addr)
12✔
1482
                if err != nil {
12✔
1483
                        t.Fatalf("unable to create output script: %v", err)
×
1484
                }
×
1485

1486
                output := &wire.TxOut{
12✔
1487
                        Value:    outputAmt,
12✔
1488
                        PkScript: script,
12✔
1489
                }
12✔
1490
                txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
12✔
1491
                if err != nil {
12✔
1492
                        t.Fatalf("unable to send coinbase: %v", err)
×
1493
                }
×
1494
                err = waitForMempoolTx(miner, txid)
12✔
1495
                if err != nil {
12✔
1496
                        t.Fatalf("tx not relayed to miner: %v", err)
×
1497
                }
×
1498
        }
1499

1500
        switch alice.BackEnd() {
4✔
1501
        case "neutrino":
1✔
1502
                // Neutrino doesn't listen for on unconfirmed transactions.
1503
        default:
3✔
1504
                // We should receive a notification for all three transactions
3✔
1505
                // generated above.
3✔
1506
                select {
3✔
1507
                case <-time.After(time.Second * 10):
×
1508
                        t.Fatalf("transactions not received after 10 seconds")
×
1509
                case err := <-errCh1:
3✔
1510
                        if err != nil {
3✔
1511
                                t.Fatal(err)
×
1512
                        }
×
1513
                }
1514
        }
1515

1516
        errCh2 := make(chan error, 1)
4✔
1517
        go func() {
8✔
1518
                for i := 0; i < numTxns; i++ {
16✔
1519
                        txDetail := <-txClient.ConfirmedTransactions()
12✔
1520
                        if txDetail.NumConfirmations != 1 {
12✔
1521
                                errCh2 <- fmt.Errorf("incorrect number of confs for %s, expected %v got %v",
×
1522
                                        txDetail.Hash, 1, txDetail.NumConfirmations)
×
1523
                                return
×
1524
                        }
×
1525
                        if txDetail.Value != outputAmt {
12✔
1526
                                errCh2 <- fmt.Errorf("incorrect output amt, expected %v got %v in txid %s",
×
1527
                                        outputAmt, txDetail.Value, txDetail.Hash)
×
1528
                                return
×
1529
                        }
×
1530
                }
1531
                errCh2 <- nil
4✔
1532
        }()
1533

1534
        // Next mine a single block, all the transactions generated above
1535
        // should be included.
1536
        if _, err := miner.Client.Generate(1); err != nil {
4✔
1537
                t.Fatalf("unable to generate block: %v", err)
×
1538
        }
×
1539

1540
        // We should receive a notification for all three transactions
1541
        // since they should be mined in the next block.
1542
        select {
4✔
1543
        case <-time.After(time.Second * 5):
×
1544
                t.Fatalf("transactions not received after 5 seconds")
×
1545
        case err := <-errCh2:
4✔
1546
                if err != nil {
4✔
1547
                        t.Fatal(err)
×
1548
                }
×
1549
        }
1550

1551
        // We'll also ensure that the client is able to send our new
1552
        // notifications when we _create_ transactions ourselves that spend our
1553
        // own outputs.
1554
        addr, err := alice.NewAddress(
4✔
1555
                lnwallet.WitnessPubKey, false,
4✔
1556
                lnwallet.DefaultAccountName,
4✔
1557
        )
4✔
1558
        require.NoError(t, err)
4✔
1559

4✔
1560
        outputScript, err := txscript.PayToAddrScript(addr)
4✔
1561
        require.NoError(t, err)
4✔
1562

4✔
1563
        burnOutput := wire.NewTxOut(outputAmt, outputScript)
4✔
1564
        tx, err := alice.SendOutputs(
4✔
1565
                nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
4✔
1566
                alice.Cfg.CoinSelectionStrategy,
4✔
1567
        )
4✔
1568
        require.NoError(t, err, "unable to create tx")
4✔
1569
        txid := tx.TxHash()
4✔
1570
        err = waitForMempoolTx(miner, &txid)
4✔
1571
        require.NoError(t, err, "tx not relayed to miner")
4✔
1572

4✔
1573
        // Before we mine the next block, we'll ensure that the above
4✔
1574
        // transaction shows up in the set of unconfirmed transactions returned
4✔
1575
        // by ListTransactionDetails.
4✔
1576
        err = waitForWalletSync(miner, alice)
4✔
1577
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1578

4✔
1579
        // As we just sent the transaction and it was landed in the mempool, we
4✔
1580
        // should get a notification for a new unconfirmed transactions
4✔
1581
        select {
4✔
1582
        case <-time.After(time.Second * 10):
×
1583
                t.Fatalf("transactions not received after 10 seconds")
×
1584
        case unConfTx := <-txClient.UnconfirmedTransactions():
4✔
1585
                if unConfTx.Hash != txid {
4✔
1586
                        t.Fatalf("wrong txn notified: expected %v got %v",
×
1587
                                txid, unConfTx.Hash)
×
1588
                }
×
1589
        }
1590
}
1591

1592
// scriptFromKey creates a P2WKH script from the given pubkey.
1593
func scriptFromKey(pubkey *btcec.PublicKey) ([]byte, error) {
66✔
1594
        pubkeyHash := btcutil.Hash160(pubkey.SerializeCompressed())
66✔
1595
        keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(
66✔
1596
                pubkeyHash, &chaincfg.RegressionNetParams,
66✔
1597
        )
66✔
1598
        if err != nil {
66✔
1599
                return nil, fmt.Errorf("unable to create addr: %w", err)
×
1600
        }
×
1601
        keyScript, err := txscript.PayToAddrScript(keyAddr)
66✔
1602
        if err != nil {
66✔
1603
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1604
        }
×
1605

1606
        return keyScript, nil
66✔
1607
}
1608

1609
// mineAndAssert mines a block and ensures the passed TX is part of that block.
1610
func mineAndAssert(r *rpctest.Harness, tx *wire.MsgTx) error {
24✔
1611
        txid := tx.TxHash()
24✔
1612
        err := waitForMempoolTx(r, &txid)
24✔
1613
        if err != nil {
24✔
1614
                return fmt.Errorf("tx not relayed to miner: %w", err)
×
1615
        }
×
1616

1617
        blockHashes, err := r.Client.Generate(1)
24✔
1618
        if err != nil {
24✔
1619
                return fmt.Errorf("unable to generate block: %w", err)
×
1620
        }
×
1621

1622
        block, err := r.Client.GetBlock(blockHashes[0])
24✔
1623
        if err != nil {
24✔
1624
                return fmt.Errorf("unable to find block: %w", err)
×
1625
        }
×
1626

1627
        if len(block.Transactions) != 2 {
24✔
1628
                return fmt.Errorf("expected 2 txs in block, got %d",
×
1629
                        len(block.Transactions))
×
1630
        }
×
1631

1632
        blockTx := block.Transactions[1]
24✔
1633
        if blockTx.TxHash() != tx.TxHash() {
24✔
1634
                return fmt.Errorf("incorrect transaction was mined")
×
1635
        }
×
1636

1637
        // Sleep for a second before returning, to make sure the block has
1638
        // propagated.
1639
        time.Sleep(1 * time.Second)
24✔
1640
        return nil
24✔
1641
}
1642

1643
// txFromOutput takes a tx paying to fromPubKey, and creates a new tx that
1644
// spends the output from this tx, to an address derived from payToPubKey.
1645
func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey,
1646
        payToPubKey *btcec.PublicKey, txFee btcutil.Amount,
1647
        rbf bool) (*wire.MsgTx, error) {
27✔
1648

27✔
1649
        // Generate the script we want to spend from.
27✔
1650
        keyScript, err := scriptFromKey(fromPubKey)
27✔
1651
        if err != nil {
27✔
1652
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1653
        }
×
1654

1655
        // We assume the output was paid to the keyScript made earlier.
1656
        var outputIndex uint32
27✔
1657
        if len(tx.TxOut) == 1 || bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
49✔
1658
                outputIndex = 0
22✔
1659
        } else {
27✔
1660
                outputIndex = 1
5✔
1661
        }
5✔
1662
        outputValue := tx.TxOut[outputIndex].Value
27✔
1663

27✔
1664
        // With the index located, we can create a transaction spending the
27✔
1665
        // referenced output.
27✔
1666
        tx1 := wire.NewMsgTx(2)
27✔
1667

27✔
1668
        // If we want to create a tx that signals replacement, set its
27✔
1669
        // sequence number to the max one that signals replacement.
27✔
1670
        // Otherwise we just use the standard max sequence.
27✔
1671
        sequence := wire.MaxTxInSequenceNum
27✔
1672
        if rbf {
39✔
1673
                sequence = mempool.MaxRBFSequence
12✔
1674
        }
12✔
1675

1676
        tx1.AddTxIn(&wire.TxIn{
27✔
1677
                PreviousOutPoint: wire.OutPoint{
27✔
1678
                        Hash:  tx.TxHash(),
27✔
1679
                        Index: outputIndex,
27✔
1680
                },
27✔
1681
                Sequence: sequence,
27✔
1682
        })
27✔
1683

27✔
1684
        // Create a script to pay to.
27✔
1685
        payToScript, err := scriptFromKey(payToPubKey)
27✔
1686
        if err != nil {
27✔
1687
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1688
        }
×
1689
        tx1.AddTxOut(&wire.TxOut{
27✔
1690
                Value:    outputValue - int64(txFee),
27✔
1691
                PkScript: payToScript,
27✔
1692
        })
27✔
1693

27✔
1694
        // Now we can populate the sign descriptor which we'll use to generate
27✔
1695
        // the signature.
27✔
1696
        signDesc := &input.SignDescriptor{
27✔
1697
                KeyDesc: keychain.KeyDescriptor{
27✔
1698
                        PubKey: fromPubKey,
27✔
1699
                },
27✔
1700
                WitnessScript: keyScript,
27✔
1701
                Output:        tx.TxOut[outputIndex],
27✔
1702
                HashType:      txscript.SigHashAll,
27✔
1703
                SigHashes:     input.NewTxSigHashesV0Only(tx1),
27✔
1704
                InputIndex:    0, // Has only one input.
27✔
1705
        }
27✔
1706

27✔
1707
        // With the descriptor created, we use it to generate a signature, then
27✔
1708
        // manually create a valid witness stack we'll use for signing.
27✔
1709
        spendSig, err := signer.SignOutputRaw(tx1, signDesc)
27✔
1710
        if err != nil {
27✔
1711
                return nil, fmt.Errorf("unable to generate signature: %w", err)
×
1712
        }
×
1713
        witness := make([][]byte, 2)
27✔
1714
        witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
27✔
1715
        witness[1] = fromPubKey.SerializeCompressed()
27✔
1716
        tx1.TxIn[0].Witness = witness
27✔
1717

27✔
1718
        // Finally, attempt to validate the completed transaction. This should
27✔
1719
        // succeed if the wallet was able to properly generate the proper
27✔
1720
        // private key.
27✔
1721
        vm, err := txscript.NewEngine(
27✔
1722
                keyScript, tx1, 0, txscript.StandardVerifyFlags, nil,
27✔
1723
                nil, outputValue, txscript.NewCannedPrevOutputFetcher(
27✔
1724
                        keyScript, outputValue,
27✔
1725
                ),
27✔
1726
        )
27✔
1727
        if err != nil {
27✔
1728
                return nil, fmt.Errorf("unable to create engine: %w", err)
×
1729
        }
×
1730
        if err := vm.Execute(); err != nil {
27✔
1731
                return nil, fmt.Errorf("spend is invalid: %w", err)
×
1732
        }
×
1733

1734
        return tx1, nil
27✔
1735
}
1736

1737
// newTx sends coins from Alice's wallet, mines this transaction, and creates a
1738
// new, unconfirmed tx that spends this output to pubKey.
1739
func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
1740
        alice *lnwallet.LightningWallet, rbf bool) *wire.MsgTx {
12✔
1741

12✔
1742
        t.Helper()
12✔
1743

12✔
1744
        keyScript, err := scriptFromKey(pubKey)
12✔
1745
        require.NoError(t, err, "unable to generate script")
12✔
1746

12✔
1747
        // Instruct the wallet to fund the output with a newly created
12✔
1748
        // transaction.
12✔
1749
        newOutput := &wire.TxOut{
12✔
1750
                Value:    btcutil.SatoshiPerBitcoin,
12✔
1751
                PkScript: keyScript,
12✔
1752
        }
12✔
1753
        tx, err := alice.SendOutputs(
12✔
1754
                nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
12✔
1755
                alice.Cfg.CoinSelectionStrategy,
12✔
1756
        )
12✔
1757
        require.NoError(t, err, "unable to create output")
12✔
1758

12✔
1759
        // Query for the transaction generated above so we can located the
12✔
1760
        // index of our output.
12✔
1761
        if err := mineAndAssert(r, tx); err != nil {
12✔
1762
                t.Fatalf("unable to mine tx: %v", err)
×
1763
        }
×
1764

1765
        // Create a new unconfirmed tx that spends this output.
1766
        txFee := btcutil.Amount(0.001 * btcutil.SatoshiPerBitcoin)
12✔
1767
        tx1, err := txFromOutput(
12✔
1768
                tx, alice.Cfg.Signer, pubKey, pubKey, txFee, rbf,
12✔
1769
        )
12✔
1770
        if err != nil {
12✔
1771
                t.Fatal(err)
×
1772
        }
×
1773

1774
        return tx1
12✔
1775
}
1776

1777
// testGetTransactionDetails checks that GetTransactionDetails returns the
1778
// correct amount after a transaction has been sent from alice to bob.
1779
func testGetTransactionDetails(r *rpctest.Harness,
1780
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
1781

4✔
1782
        const txFee = int64(14500)
4✔
1783

4✔
1784
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
4✔
1785
        txFeeRate := chainfee.SatPerKWeight(2500)
4✔
1786
        amountSats := btcutil.Amount(btcutil.SatoshiPerBitcoin - txFee)
4✔
1787
        output := &wire.TxOut{
4✔
1788
                Value:    int64(amountSats),
4✔
1789
                PkScript: bobPkScript,
4✔
1790
        }
4✔
1791
        tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
1792
        txHash := tx.TxHash()
4✔
1793
        assertTxInWallet(t, alice, txHash, true)
4✔
1794
        assertTxInWallet(t, bob, txHash, true)
4✔
1795

4✔
1796
        txDetails, err := bob.GetTransactionDetails(&txHash)
4✔
1797
        require.NoError(t, err, "unable to receive transaction details")
4✔
1798
        require.Equal(t, txDetails.Value, amountSats, "tx value")
4✔
1799
}
4✔
1800

1801
// testPublishTransaction checks that PublishTransaction returns the expected
1802
// error types in case the transaction being published conflicts with the
1803
// current mempool or chain.
1804
func testPublishTransaction(r *rpctest.Harness,
1805
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1806

4✔
1807
        // Generate a pubkey, and pay-to-addr script.
4✔
1808
        keyDesc, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
4✔
1809
        require.NoError(t, err, "unable to obtain public key")
4✔
1810

4✔
1811
        t.Run("no_error_on_duplicate_tx", func(t *testing.T) {
8✔
1812
                // We will first check that publishing a transaction already in
4✔
1813
                // the mempool does NOT return an error. Create the tx.
4✔
1814
                tx1 := newTx(t, r, keyDesc.PubKey, alice, false)
4✔
1815

4✔
1816
                // Publish the transaction.
4✔
1817
                err = alice.PublishTransaction(tx1, labels.External)
4✔
1818
                require.NoError(t, err)
4✔
1819

4✔
1820
                txid1 := tx1.TxHash()
4✔
1821
                err = waitForMempoolTx(r, &txid1)
4✔
1822
                require.NoError(t, err, "tx not relayed to miner")
4✔
1823

4✔
1824
                // Publish the exact same transaction again. This should not
4✔
1825
                // return an error, even though the transaction is already in
4✔
1826
                // the mempool.
4✔
1827
                err = alice.PublishTransaction(tx1, labels.External)
4✔
1828
                require.NoError(t, err)
4✔
1829

4✔
1830
                // Mine the transaction.
4✔
1831
                _, err := r.Client.Generate(1)
4✔
1832
                require.NoError(t, err)
4✔
1833
        })
4✔
1834

1835
        t.Run("no_error_on_minged_tx", func(t *testing.T) {
8✔
1836
                // We'll now test that we don't get an error if we try to
4✔
1837
                // publish a transaction that is already mined.
4✔
1838
                //
4✔
1839
                // Create a new transaction. We must do this to properly test
4✔
1840
                // the reject messages from our peers. They might only send us
4✔
1841
                // a reject message for a given tx once, so we create a new to
4✔
1842
                // make sure it is not just immediately rejected.
4✔
1843
                tx2 := newTx(t, r, keyDesc.PubKey, alice, false)
4✔
1844

4✔
1845
                // Publish this tx.
4✔
1846
                err = alice.PublishTransaction(tx2, labels.External)
4✔
1847
                require.NoError(t, err)
4✔
1848

4✔
1849
                // Mine the transaction.
4✔
1850
                err := mineAndAssert(r, tx2)
4✔
1851
                require.NoError(t, err)
4✔
1852

4✔
1853
                // Publish the transaction again. It is already mined, and we
4✔
1854
                // don't expect this to return an error.
4✔
1855
                err = alice.PublishTransaction(tx2, labels.External)
4✔
1856
                require.NoError(t, err)
4✔
1857
        })
4✔
1858

1859
        // We'll do the next mempool check on both RBF and non-RBF
1860
        // enabled transactions.
1861
        var (
4✔
1862
                txFee = btcutil.Amount(
4✔
1863
                        0.005 * btcutil.SatoshiPerBitcoin,
4✔
1864
                )
4✔
1865
                tx3, tx3Spend *wire.MsgTx
4✔
1866
        )
4✔
1867
        t.Run("rbf_tests", func(t *testing.T) {
8✔
1868
                // Starting with bitcoind v28.0 and later, mempool full RBF is
4✔
1869
                // turned on, so there's no way to _not_ signal RBF anymore.
4✔
1870
                const rbf = true
4✔
1871

4✔
1872
                // Now we'll try to double spend an output with a
4✔
1873
                // different transaction. Create a new tx and publish
4✔
1874
                // it. This is the output we'll try to double spend.
4✔
1875
                tx3 = newTx(t, r, keyDesc.PubKey, alice, false)
4✔
1876
                err := alice.PublishTransaction(tx3, labels.External)
4✔
1877
                require.NoError(t, err)
4✔
1878

4✔
1879
                // Mine the transaction.
4✔
1880
                err = mineAndAssert(r, tx3)
4✔
1881
                require.NoError(t, err)
4✔
1882

4✔
1883
                // Now we create a transaction that spends the output
4✔
1884
                // from the tx just mined.
4✔
1885
                tx4, err := txFromOutput(
4✔
1886
                        tx3, alice.Cfg.Signer, keyDesc.PubKey,
4✔
1887
                        keyDesc.PubKey, txFee, rbf,
4✔
1888
                )
4✔
1889
                require.NoError(t, err)
4✔
1890

4✔
1891
                // This should be accepted into the mempool.
4✔
1892
                err = alice.PublishTransaction(tx4, labels.External)
4✔
1893
                require.NoError(t, err)
4✔
1894

4✔
1895
                // Keep track of the last successfully published tx to
4✔
1896
                // spend tx3.
4✔
1897
                tx3Spend = tx4
4✔
1898

4✔
1899
                txid4 := tx4.TxHash()
4✔
1900
                err = waitForMempoolTx(r, &txid4)
4✔
1901
                require.NoError(t, err, "tx not relayed to miner")
4✔
1902

4✔
1903
                // Create a new key we'll pay to, to ensure we create a
4✔
1904
                // unique transaction.
4✔
1905
                keyDesc2, err := alice.DeriveNextKey(
4✔
1906
                        keychain.KeyFamilyMultiSig,
4✔
1907
                )
4✔
1908
                require.NoError(t, err, "unable to obtain public key")
4✔
1909

4✔
1910
                // Create a new transaction that spends the output from
4✔
1911
                // tx3, and that pays to a different address.
4✔
1912
                tx5, err := txFromOutput(
4✔
1913
                        tx3, alice.Cfg.Signer, keyDesc.PubKey,
4✔
1914
                        keyDesc2.PubKey, txFee, rbf,
4✔
1915
                )
4✔
1916
                require.NoError(t, err)
4✔
1917

4✔
1918
                err = alice.PublishTransaction(tx5, labels.External)
4✔
1919

4✔
1920
                // We expect it to be rejected/ because it doesn't pay enough
4✔
1921
                // fees.
4✔
1922
                expectedErr := chain.ErrInsufficientFee
4✔
1923

4✔
1924
                // Assert the expected error.
4✔
1925
                require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf)
4✔
1926

4✔
1927
                // Create another transaction that spends the same
4✔
1928
                // output, but has a higher fee. We expect also this tx
4✔
1929
                // to be rejected for non-RBF enabled transactions,
4✔
1930
                // while it should succeed otherwise.
4✔
1931
                pubKey3, err := alice.DeriveNextKey(
4✔
1932
                        keychain.KeyFamilyMultiSig,
4✔
1933
                )
4✔
1934
                require.NoError(t, err, "unable to obtain public key")
4✔
1935

4✔
1936
                tx6, err := txFromOutput(
4✔
1937
                        tx3, alice.Cfg.Signer, keyDesc.PubKey,
4✔
1938
                        pubKey3.PubKey, 2*txFee, rbf,
4✔
1939
                )
4✔
1940
                require.NoError(t, err)
4✔
1941

4✔
1942
                // Expect rejection in non-RBF case.
4✔
1943
                tx3Spend = tx6
4✔
1944
                err = alice.PublishTransaction(tx6, labels.External)
4✔
1945
                require.NoError(t, err)
4✔
1946

4✔
1947
                // Mine the tx spending tx3.
4✔
1948
                err = mineAndAssert(r, tx3Spend)
4✔
1949
                require.NoError(t, err)
4✔
1950
        })
4✔
1951

1952
        t.Run("tx_double_spend", func(t *testing.T) {
8✔
1953
                // At last we try to spend an output already spent by a
4✔
1954
                // confirmed transaction.
4✔
1955
                //
4✔
1956
                // TODO(halseth): we currently skip this test for neutrino, as
4✔
1957
                // the backing btcd node will consider the tx being an orphan,
4✔
1958
                // and will accept it. Should look into if this is the behavior
4✔
1959
                // also for bitcoind, and update test accordingly.
4✔
1960
                if alice.BackEnd() != "neutrino" {
7✔
1961
                        // Create another tx spending tx3.
3✔
1962
                        pubKey4, err := alice.DeriveNextKey(
3✔
1963
                                keychain.KeyFamilyMultiSig,
3✔
1964
                        )
3✔
1965
                        require.NoError(t, err, "unable to obtain public key")
3✔
1966

3✔
1967
                        tx7, err := txFromOutput(
3✔
1968
                                tx3, alice.Cfg.Signer, keyDesc.PubKey,
3✔
1969
                                pubKey4.PubKey, txFee, false,
3✔
1970
                        )
3✔
1971
                        require.NoError(t, err)
3✔
1972

3✔
1973
                        // Expect rejection.
3✔
1974
                        err = alice.PublishTransaction(tx7, labels.External)
3✔
1975
                        require.ErrorIs(t, err, lnwallet.ErrDoubleSpend)
3✔
1976
                }
3✔
1977
        })
1978

1979
        t.Run("test_tx_size_limit", func(t *testing.T) {
8✔
1980
                // In this test, we'll try to create a massive transaction that
4✔
1981
                // can't be mined as dictacted by widely deployed transaction
4✔
1982
                // policy.
4✔
1983
                //
4✔
1984
                // To do this, we'll take out of the prior transactions, and
4✔
1985
                // add a bunch of outputs, putting it over the max weight
4✔
1986
                // limit.
4✔
1987
                testTx := tx3.Copy()
4✔
1988
                for i := 0; i < blockchain.MaxOutputsPerBlock; i++ {
444,448✔
1989
                        testTx.AddTxOut(&wire.TxOut{
444,444✔
1990
                                Value:    tx3.TxOut[0].Value,
444,444✔
1991
                                PkScript: tx3.TxOut[0].PkScript,
444,444✔
1992
                        })
444,444✔
1993
                }
444,444✔
1994

1995
                // Now broadcast the transaction, we should get an error that
1996
                // the weight is too large.
1997
                err := alice.PublishTransaction(testTx, labels.External)
4✔
1998
                require.ErrorIs(t, err, chain.ErrOversizeTx)
4✔
1999
        })
2000
}
2001

2002
func testSignOutputUsingTweaks(r *rpctest.Harness,
2003
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2004

4✔
2005
        // We'd like to test the ability of the wallet's Signer implementation
4✔
2006
        // to be able to sign with a private key derived from tweaking the
4✔
2007
        // specific public key. This scenario exercises the case when the
4✔
2008
        // wallet needs to sign for a sweep of a revoked output, or just claim
4✔
2009
        // any output that pays to a tweaked key.
4✔
2010

4✔
2011
        // First, generate a new public key under the control of the wallet,
4✔
2012
        // then generate a revocation key using it.
4✔
2013
        pubKey, err := alice.DeriveNextKey(
4✔
2014
                keychain.KeyFamilyMultiSig,
4✔
2015
        )
4✔
2016
        require.NoError(t, err, "unable to obtain public key")
4✔
2017

4✔
2018
        // As we'd like to test both single tweak, and double tweak spends,
4✔
2019
        // we'll generate a commitment pre-image, then derive a revocation key
4✔
2020
        // and single tweak from that.
4✔
2021
        commitPreimage := bytes.Repeat([]byte{2}, 32)
4✔
2022
        commitSecret, commitPoint := btcec.PrivKeyFromBytes(commitPreimage)
4✔
2023

4✔
2024
        revocationKey := input.DeriveRevocationPubkey(pubKey.PubKey, commitPoint)
4✔
2025
        commitTweak := input.SingleTweakBytes(commitPoint, pubKey.PubKey)
4✔
2026

4✔
2027
        tweakedPub := input.TweakPubKey(pubKey.PubKey, commitPoint)
4✔
2028

4✔
2029
        // As we'd like to test both single and double tweaks, we'll repeat
4✔
2030
        // the same set up twice. The first will use a regular single tweak,
4✔
2031
        // and the second will use a double tweak.
4✔
2032
        baseKey := pubKey
4✔
2033
        for i := 0; i < 2; i++ {
12✔
2034
                var tweakedKey *btcec.PublicKey
8✔
2035
                if i == 0 {
12✔
2036
                        tweakedKey = tweakedPub
4✔
2037
                } else {
8✔
2038
                        tweakedKey = revocationKey
4✔
2039
                }
4✔
2040

2041
                // Using the given key for the current iteration, we'll
2042
                // generate a regular p2wkh from that.
2043
                pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
8✔
2044
                keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
8✔
2045
                        &chaincfg.RegressionNetParams)
8✔
2046
                if err != nil {
8✔
2047
                        t.Fatalf("unable to create addr: %v", err)
×
2048
                }
×
2049
                keyScript, err := txscript.PayToAddrScript(keyAddr)
8✔
2050
                if err != nil {
8✔
2051
                        t.Fatalf("unable to generate script: %v", err)
×
2052
                }
×
2053

2054
                // With the script fully assembled, instruct the wallet to fund
2055
                // the output with a newly created transaction.
2056
                newOutput := &wire.TxOut{
8✔
2057
                        Value:    btcutil.SatoshiPerBitcoin,
8✔
2058
                        PkScript: keyScript,
8✔
2059
                }
8✔
2060
                tx, err := alice.SendOutputs(
8✔
2061
                        nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
8✔
2062
                        alice.Cfg.CoinSelectionStrategy,
8✔
2063
                )
8✔
2064
                if err != nil {
8✔
2065
                        t.Fatalf("unable to create output: %v", err)
×
2066
                }
×
2067
                txid := tx.TxHash()
8✔
2068
                // Query for the transaction generated above so we can located
8✔
2069
                // the index of our output.
8✔
2070
                err = waitForMempoolTx(r, &txid)
8✔
2071
                if err != nil {
8✔
2072
                        t.Fatalf("tx not relayed to miner: %v", err)
×
2073
                }
×
2074
                var outputIndex uint32
8✔
2075
                if bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
9✔
2076
                        outputIndex = 0
1✔
2077
                } else {
8✔
2078
                        outputIndex = 1
7✔
2079
                }
7✔
2080

2081
                // With the index located, we can create a transaction spending
2082
                // the referenced output.
2083
                sweepTx := wire.NewMsgTx(2)
8✔
2084
                sweepTx.AddTxIn(&wire.TxIn{
8✔
2085
                        PreviousOutPoint: wire.OutPoint{
8✔
2086
                                Hash:  txid,
8✔
2087
                                Index: outputIndex,
8✔
2088
                        },
8✔
2089
                })
8✔
2090
                sweepTx.AddTxOut(&wire.TxOut{
8✔
2091
                        Value:    1000,
8✔
2092
                        PkScript: keyScript,
8✔
2093
                })
8✔
2094

8✔
2095
                // Now we can populate the sign descriptor which we'll use to
8✔
2096
                // generate the signature. Within the descriptor we set the
8✔
2097
                // private tweak value as the key in the script is derived
8✔
2098
                // based on this tweak value and the key we originally
8✔
2099
                // generated above.
8✔
2100
                signDesc := &input.SignDescriptor{
8✔
2101
                        KeyDesc: keychain.KeyDescriptor{
8✔
2102
                                PubKey: baseKey.PubKey,
8✔
2103
                        },
8✔
2104
                        WitnessScript: keyScript,
8✔
2105
                        Output:        newOutput,
8✔
2106
                        HashType:      txscript.SigHashAll,
8✔
2107
                        SigHashes:     input.NewTxSigHashesV0Only(sweepTx),
8✔
2108
                        InputIndex:    0,
8✔
2109
                }
8✔
2110

8✔
2111
                // If this is the first, loop, we'll use the generated single
8✔
2112
                // tweak, otherwise, we'll use the double tweak.
8✔
2113
                if i == 0 {
12✔
2114
                        signDesc.SingleTweak = commitTweak
4✔
2115
                } else {
8✔
2116
                        signDesc.DoubleTweak = commitSecret
4✔
2117
                }
4✔
2118

2119
                // With the descriptor created, we use it to generate a
2120
                // signature, then manually create a valid witness stack we'll
2121
                // use for signing.
2122
                spendSig, err := alice.Cfg.Signer.SignOutputRaw(sweepTx, signDesc)
8✔
2123
                if err != nil {
8✔
2124
                        t.Fatalf("unable to generate signature: %v", err)
×
2125
                }
×
2126
                witness := make([][]byte, 2)
8✔
2127
                witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
8✔
2128
                witness[1] = tweakedKey.SerializeCompressed()
8✔
2129
                sweepTx.TxIn[0].Witness = witness
8✔
2130

8✔
2131
                // Finally, attempt to validate the completed transaction. This
8✔
2132
                // should succeed if the wallet was able to properly generate
8✔
2133
                // the proper private key.
8✔
2134
                vm, err := txscript.NewEngine(
8✔
2135
                        keyScript, sweepTx, 0, txscript.StandardVerifyFlags,
8✔
2136
                        nil, nil, int64(btcutil.SatoshiPerBitcoin),
8✔
2137
                        txscript.NewCannedPrevOutputFetcher(
8✔
2138
                                keyScript, int64(btcutil.SatoshiPerBitcoin),
8✔
2139
                        ),
8✔
2140
                )
8✔
2141
                if err != nil {
8✔
2142
                        t.Fatalf("unable to create engine: %v", err)
×
2143
                }
×
2144
                if err := vm.Execute(); err != nil {
8✔
2145
                        t.Fatalf("spend #%v is invalid: %v", i, err)
×
2146
                }
×
2147
        }
2148
}
2149

2150
func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
2151
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2152

4✔
2153
        // We first mine a few blocks to ensure any transactions still in the
4✔
2154
        // mempool confirm, and then get the original balance, before a
4✔
2155
        // reorganization that doesn't invalidate any existing transactions or
4✔
2156
        // create any new non-coinbase transactions. We'll then check if it's
4✔
2157
        // the same after the empty reorg.
4✔
2158
        _, err := r.Client.Generate(5)
4✔
2159
        require.NoError(t, err, "unable to generate blocks on passed node")
4✔
2160

4✔
2161
        // Give wallet time to catch up.
4✔
2162
        err = waitForWalletSync(r, w)
4✔
2163
        require.NoError(t, err, "unable to sync wallet")
4✔
2164

4✔
2165
        // Send some money from the miner to the wallet
4✔
2166
        err = loadTestCredits(r, w, 20, 4)
4✔
2167
        require.NoError(t, err, "unable to send money to lnwallet")
4✔
2168

4✔
2169
        // Send some money from the wallet back to the miner.
4✔
2170
        // Grab a fresh address from the miner to house this output.
4✔
2171
        minerAddr, err := r.NewAddress()
4✔
2172
        require.NoError(t, err, "unable to generate address for miner")
4✔
2173
        script, err := txscript.PayToAddrScript(minerAddr)
4✔
2174
        require.NoError(t, err, "unable to create pay to addr script")
4✔
2175
        output := &wire.TxOut{
4✔
2176
                Value:    1e8,
4✔
2177
                PkScript: script,
4✔
2178
        }
4✔
2179
        tx, err := w.SendOutputs(
4✔
2180
                nil, []*wire.TxOut{output}, 2500, 1, labels.External,
4✔
2181
                w.Cfg.CoinSelectionStrategy,
4✔
2182
        )
4✔
2183
        require.NoError(t, err, "unable to send outputs")
4✔
2184
        txid := tx.TxHash()
4✔
2185
        err = waitForMempoolTx(r, &txid)
4✔
2186
        require.NoError(t, err, "tx not relayed to miner")
4✔
2187
        _, err = r.Client.Generate(50)
4✔
2188
        require.NoError(t, err, "unable to generate blocks on passed node")
4✔
2189

4✔
2190
        // Give wallet time to catch up.
4✔
2191
        err = waitForWalletSync(r, w)
4✔
2192
        require.NoError(t, err, "unable to sync wallet")
4✔
2193

4✔
2194
        // Get the original balance.
4✔
2195
        origBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2196
        require.NoError(t, err, "unable to query for balance")
4✔
2197

4✔
2198
        // Now we cause a reorganization as follows.
4✔
2199
        // Step 1: create a new miner and start it.
4✔
2200
        r2 := unittest.NewMiner(
4✔
2201
                t, r.ActiveNet, []string{"--txindex"}, false, 0,
4✔
2202
        )
4✔
2203
        newBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2204
        require.NoError(t, err, "unable to query for balance")
4✔
2205
        if origBalance != newBalance {
4✔
2206
                t.Fatalf("wallet balance incorrect, should have %v, "+
×
2207
                        "instead have %v", origBalance, newBalance)
×
2208
        }
×
2209

2210
        // Step 2: connect the miner to the passed miner and wait for
2211
        // synchronization.
2212
        err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
4✔
2213
        require.NoError(t, err, "unable to connect mining nodes together")
4✔
2214
        err = rpctest.JoinNodes([]*rpctest.Harness{r2, r}, rpctest.Blocks)
4✔
2215
        require.NoError(t, err, "unable to synchronize mining nodes")
4✔
2216

4✔
2217
        // Step 3: Do a set of reorgs by disconnecting the two miners, mining
4✔
2218
        // one block on the passed miner and two on the created miner,
4✔
2219
        // connecting them, and waiting for them to sync.
4✔
2220
        for i := 0; i < 5; i++ {
24✔
2221
                // Wait for disconnection
20✔
2222
                timeout := time.After(30 * time.Second)
20✔
2223
                stillConnected := true
20✔
2224
                var peers []btcjson.GetPeerInfoResult
20✔
2225
                for stillConnected {
41✔
2226
                        // Allow for timeout
21✔
2227
                        time.Sleep(100 * time.Millisecond)
21✔
2228
                        select {
21✔
2229
                        case <-timeout:
×
2230
                                t.Fatalf("timeout waiting for miner disconnect")
×
2231
                        default:
21✔
2232
                        }
2233
                        err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANRemove)
21✔
2234
                        if err != nil {
21✔
2235
                                t.Fatalf("unable to disconnect mining nodes: %v",
×
2236
                                        err)
×
2237
                        }
×
2238
                        peers, err = r2.Client.GetPeerInfo()
21✔
2239
                        if err != nil {
21✔
2240
                                t.Fatalf("unable to get peer info: %v", err)
×
2241
                        }
×
2242
                        stillConnected = false
21✔
2243
                        for _, peer := range peers {
22✔
2244
                                if peer.Addr == r.P2PAddress() {
2✔
2245
                                        stillConnected = true
1✔
2246
                                        break
1✔
2247
                                }
2248
                        }
2249
                }
2250
                _, err = r.Client.Generate(2)
20✔
2251
                if err != nil {
20✔
2252
                        t.Fatalf("unable to generate blocks on passed node: %v",
×
2253
                                err)
×
2254
                }
×
2255
                _, err = r2.Client.Generate(3)
20✔
2256
                if err != nil {
20✔
2257
                        t.Fatalf("unable to generate blocks on created node: %v",
×
2258
                                err)
×
2259
                }
×
2260

2261
                // Step 5: Reconnect the miners and wait for them to synchronize.
2262
                err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
20✔
2263
                if err != nil {
20✔
2264
                        switch err := err.(type) {
×
2265
                        case *btcjson.RPCError:
×
2266
                                if err.Code != -8 {
×
2267
                                        t.Fatalf("unable to connect mining "+
×
2268
                                                "nodes together: %v", err)
×
2269
                                }
×
2270
                        default:
×
2271
                                t.Fatalf("unable to connect mining nodes "+
×
2272
                                        "together: %v", err)
×
2273
                        }
2274
                }
2275
                err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
20✔
2276
                        rpctest.Blocks)
20✔
2277
                if err != nil {
20✔
2278
                        t.Fatalf("unable to synchronize mining nodes: %v", err)
×
2279
                }
×
2280

2281
                // Give wallet time to catch up.
2282
                err = waitForWalletSync(r, w)
20✔
2283
                if err != nil {
20✔
2284
                        t.Fatalf("unable to sync wallet: %v", err)
×
2285
                }
×
2286
        }
2287

2288
        // Now we check that the wallet balance stays the same.
2289
        newBalance, err = w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2290
        require.NoError(t, err, "unable to query for balance")
4✔
2291
        if origBalance != newBalance {
4✔
2292
                t.Fatalf("wallet balance incorrect, should have %v, "+
×
2293
                        "instead have %v", origBalance, newBalance)
×
2294
        }
×
2295
}
2296

2297
// testChangeOutputSpendConfirmation ensures that when we attempt to spend a
2298
// change output created by the wallet, the wallet receives its confirmation
2299
// once included in the chain.
2300
func testChangeOutputSpendConfirmation(r *rpctest.Harness,
2301
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2302

4✔
2303
        // In order to test that we see the confirmation of a transaction that
4✔
2304
        // spends an output created by SendOutputs, we'll start by emptying
4✔
2305
        // Alice's wallet so that no other UTXOs can be picked. To do so, we'll
4✔
2306
        // generate an address for Bob, who will receive all the coins.
4✔
2307
        // Assuming a balance of 80 BTC and a transaction fee of 2500 sat/kw,
4✔
2308
        // we'll craft the following transaction so that Alice doesn't have any
4✔
2309
        // UTXOs left.
4✔
2310
        aliceBalance, err := alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
4✔
2311
        require.NoError(t, err, "unable to retrieve alice's balance")
4✔
2312
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
4✔
2313

4✔
2314
        // We'll use a transaction fee of 14380 satoshis, which will allow us to
4✔
2315
        // sweep all of Alice's balance in one transaction containing 1 input
4✔
2316
        // and 1 output.
4✔
2317
        //
4✔
2318
        // TODO(wilmer): replace this once SendOutputs easily supports sending
4✔
2319
        // all funds in one transaction.
4✔
2320
        txFeeRate := chainfee.SatPerKWeight(2500)
4✔
2321
        const txFee = int64(14500)
4✔
2322
        output := &wire.TxOut{
4✔
2323
                Value:    int64(aliceBalance) - txFee,
4✔
2324
                PkScript: bobPkScript,
4✔
2325
        }
4✔
2326
        tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2327
        txHash := tx.TxHash()
4✔
2328
        assertTxInWallet(t, alice, txHash, true)
4✔
2329
        assertTxInWallet(t, bob, txHash, true)
4✔
2330

4✔
2331
        // With the transaction sent and confirmed, Alice's balance should now
4✔
2332
        // be 0.
4✔
2333
        aliceBalance, err = alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
4✔
2334
        require.NoError(t, err, "unable to retrieve alice's balance")
4✔
2335
        if aliceBalance != 0 {
4✔
2336
                t.Fatalf("expected alice's balance to be 0 BTC, found %v",
×
2337
                        aliceBalance)
×
2338
        }
×
2339

2340
        // Now, we'll send an output back to Alice from Bob of 1 BTC.
2341
        alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
4✔
2342
        output = &wire.TxOut{
4✔
2343
                Value:    btcutil.SatoshiPerBitcoin,
4✔
2344
                PkScript: alicePkScript,
4✔
2345
        }
4✔
2346
        tx = sendCoins(t, r, bob, alice, output, txFeeRate, true, 1)
4✔
2347
        txHash = tx.TxHash()
4✔
2348
        assertTxInWallet(t, alice, txHash, true)
4✔
2349
        assertTxInWallet(t, bob, txHash, true)
4✔
2350

4✔
2351
        // Alice now has an available output to spend, but it was not a change
4✔
2352
        // output, which is what the test expects. Therefore, we'll generate one
4✔
2353
        // by sending Bob back some coins.
4✔
2354
        output = &wire.TxOut{
4✔
2355
                Value:    btcutil.SatoshiPerBitcent,
4✔
2356
                PkScript: bobPkScript,
4✔
2357
        }
4✔
2358
        tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2359
        txHash = tx.TxHash()
4✔
2360
        assertTxInWallet(t, alice, txHash, true)
4✔
2361
        assertTxInWallet(t, bob, txHash, true)
4✔
2362

4✔
2363
        // Then, we'll spend the change output and ensure we see its
4✔
2364
        // confirmation come in.
4✔
2365
        tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2366
        txHash = tx.TxHash()
4✔
2367
        assertTxInWallet(t, alice, txHash, true)
4✔
2368
        assertTxInWallet(t, bob, txHash, true)
4✔
2369

4✔
2370
        // Finally, we'll replenish Alice's wallet with some more coins to
4✔
2371
        // ensure she has enough for any following test cases.
4✔
2372
        if err := loadTestCredits(r, alice, 20, 4); err != nil {
4✔
2373
                t.Fatalf("unable to replenish alice's wallet: %v", err)
×
2374
        }
×
2375
}
2376

2377
// testSpendUnconfirmed ensures that when can spend unconfirmed outputs.
2378
func testSpendUnconfirmed(miner *rpctest.Harness,
2379
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
3✔
2380

3✔
2381
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
3✔
2382
        alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
3✔
2383
        txFeeRate := chainfee.SatPerKWeight(2500)
3✔
2384

3✔
2385
        // First we will empty out bob's wallet, sending the entire balance
3✔
2386
        // to alice.
3✔
2387
        bobBalance, err := bob.ConfirmedBalance(0, lnwallet.DefaultAccountName)
3✔
2388
        require.NoError(t, err, "unable to retrieve bob's balance")
3✔
2389
        txFee := btcutil.Amount(28760)
3✔
2390
        output := &wire.TxOut{
3✔
2391
                Value:    int64(bobBalance - txFee),
3✔
2392
                PkScript: alicePkScript,
3✔
2393
        }
3✔
2394
        tx := sendCoins(t, miner, bob, alice, output, txFeeRate, true, 1)
3✔
2395
        txHash := tx.TxHash()
3✔
2396
        assertTxInWallet(t, alice, txHash, true)
3✔
2397
        assertTxInWallet(t, bob, txHash, true)
3✔
2398

3✔
2399
        // Verify that bob doesn't have enough balance to send coins.
3✔
2400
        output = &wire.TxOut{
3✔
2401
                Value:    btcutil.SatoshiPerBitcoin * 0.5,
3✔
2402
                PkScript: alicePkScript,
3✔
2403
        }
3✔
2404
        _, err = bob.SendOutputs(
3✔
2405
                nil, []*wire.TxOut{output}, txFeeRate, 0, labels.External,
3✔
2406
                bob.Cfg.CoinSelectionStrategy,
3✔
2407
        )
3✔
2408
        if err == nil {
3✔
2409
                t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
×
2410
        }
×
2411

2412
        // Next we will send a transaction to bob but leave it in an
2413
        // unconfirmed state.
2414
        output = &wire.TxOut{
3✔
2415
                Value:    btcutil.SatoshiPerBitcoin,
3✔
2416
                PkScript: bobPkScript,
3✔
2417
        }
3✔
2418
        tx = sendCoins(t, miner, alice, bob, output, txFeeRate, false, 1)
3✔
2419
        txHash = tx.TxHash()
3✔
2420
        assertTxInWallet(t, alice, txHash, false)
3✔
2421
        assertTxInWallet(t, bob, txHash, false)
3✔
2422

3✔
2423
        // Now, try to spend some of the unconfirmed funds from bob's wallet.
3✔
2424
        output = &wire.TxOut{
3✔
2425
                Value:    btcutil.SatoshiPerBitcoin * 0.5,
3✔
2426
                PkScript: alicePkScript,
3✔
2427
        }
3✔
2428

3✔
2429
        // First, verify that we don't have enough balance to send the coins
3✔
2430
        // using confirmed outputs only.
3✔
2431
        _, err = bob.SendOutputs(
3✔
2432
                nil, []*wire.TxOut{output}, txFeeRate, 1, labels.External,
3✔
2433
                bob.Cfg.CoinSelectionStrategy,
3✔
2434
        )
3✔
2435
        if err == nil {
3✔
2436
                t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
×
2437
        }
×
2438

2439
        // Now try the send again using unconfirmed outputs.
2440
        tx = sendCoins(t, miner, bob, alice, output, txFeeRate, false, 0)
3✔
2441
        txHash = tx.TxHash()
3✔
2442
        assertTxInWallet(t, alice, txHash, false)
3✔
2443
        assertTxInWallet(t, bob, txHash, false)
3✔
2444

3✔
2445
        // Mine the unconfirmed transactions.
3✔
2446
        err = waitForMempoolTx(miner, &txHash)
3✔
2447
        require.NoError(t, err, "tx not relayed to miner")
3✔
2448
        if _, err := miner.Client.Generate(1); err != nil {
3✔
2449
                t.Fatalf("unable to generate block: %v", err)
×
2450
        }
×
2451
        if err := waitForWalletSync(miner, alice); err != nil {
3✔
2452
                t.Fatalf("unable to sync alice: %v", err)
×
2453
        }
×
2454
        if err := waitForWalletSync(miner, bob); err != nil {
3✔
2455
                t.Fatalf("unable to sync bob: %v", err)
×
2456
        }
×
2457

2458
        // Finally, send the remainder of bob's wallet balance back to him so
2459
        // that these money movements dont mess up later tests.
2460
        output = &wire.TxOut{
3✔
2461
                Value:    int64(bobBalance) - (btcutil.SatoshiPerBitcoin * 0.4),
3✔
2462
                PkScript: bobPkScript,
3✔
2463
        }
3✔
2464
        tx = sendCoins(t, miner, alice, bob, output, txFeeRate, true, 1)
3✔
2465
        txHash = tx.TxHash()
3✔
2466
        assertTxInWallet(t, alice, txHash, true)
3✔
2467
        assertTxInWallet(t, bob, txHash, true)
3✔
2468
}
2469

2470
// testLastUnusedAddr tests that the LastUnusedAddress returns the address if
2471
// it isn't used, and also that once the address becomes used, then it's
2472
// properly rotated.
2473
func testLastUnusedAddr(miner *rpctest.Harness,
2474
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2475

4✔
2476
        if _, err := miner.Client.Generate(1); err != nil {
4✔
2477
                t.Fatalf("unable to generate block: %v", err)
×
2478
        }
×
2479

2480
        // We'll repeat this test for each address type to ensure they're all
2481
        // rotated properly.
2482
        addrTypes := []lnwallet.AddressType{
4✔
2483
                lnwallet.WitnessPubKey, lnwallet.NestedWitnessPubKey,
4✔
2484
        }
4✔
2485
        for _, addrType := range addrTypes {
12✔
2486
                addr1, err := alice.LastUnusedAddress(
8✔
2487
                        addrType, lnwallet.DefaultAccountName,
8✔
2488
                )
8✔
2489
                if err != nil {
8✔
2490
                        t.Fatalf("unable to get addr: %v", err)
×
2491
                }
×
2492
                addr2, err := alice.LastUnusedAddress(
8✔
2493
                        addrType, lnwallet.DefaultAccountName,
8✔
2494
                )
8✔
2495
                if err != nil {
8✔
2496
                        t.Fatalf("unable to get addr: %v", err)
×
2497
                }
×
2498

2499
                // If we generate two addresses back to back, then we should
2500
                // get the same addr, as none of them have been used yet.
2501
                if addr1.String() != addr2.String() {
8✔
2502
                        t.Fatalf("addresses changed w/o use: %v vs %v", addr1, addr2)
×
2503
                }
×
2504

2505
                // Next, we'll have Bob pay to Alice's new address. This should
2506
                // trigger address rotation at the backend wallet.
2507
                addrScript, err := txscript.PayToAddrScript(addr1)
8✔
2508
                if err != nil {
8✔
2509
                        t.Fatalf("unable to convert addr to script: %v", err)
×
2510
                }
×
2511
                feeRate := chainfee.SatPerKWeight(2500)
8✔
2512
                output := &wire.TxOut{
8✔
2513
                        Value:    1000000,
8✔
2514
                        PkScript: addrScript,
8✔
2515
                }
8✔
2516
                sendCoins(t, miner, bob, alice, output, feeRate, true, 1)
8✔
2517

8✔
2518
                // If we make a new address, then it should be brand new, as
8✔
2519
                // the prior address has been used.
8✔
2520
                addr3, err := alice.LastUnusedAddress(
8✔
2521
                        addrType, lnwallet.DefaultAccountName,
8✔
2522
                )
8✔
2523
                if err != nil {
8✔
2524
                        t.Fatalf("unable to get addr: %v", err)
×
2525
                }
×
2526
                if addr1.String() == addr3.String() {
8✔
2527
                        t.Fatalf("address should have changed but didn't")
×
2528
                }
×
2529
        }
2530
}
2531

2532
// testCreateSimpleTx checks that a call to CreateSimpleTx will return a
2533
// transaction that is equal to the one that is being created by SendOutputs in
2534
// a subsequent call.
2535
// All test cases are doubled-up: one for testing unconfirmed inputs,
2536
// one for testing only confirmed inputs.
2537
func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
2538
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2539

4✔
2540
        // Send some money from the miner to the wallet
4✔
2541
        err := loadTestCredits(r, w, 20, 4)
4✔
2542
        require.NoError(t, err, "unable to send money to lnwallet")
4✔
2543

4✔
2544
        // The test cases we will run through for all backends.
4✔
2545
        testCases := []struct {
4✔
2546
                outVals     []int64
4✔
2547
                feeRate     chainfee.SatPerKWeight
4✔
2548
                valid       bool
4✔
2549
                unconfirmed bool
4✔
2550
        }{
4✔
2551
                {
4✔
2552
                        outVals:     []int64{},
4✔
2553
                        feeRate:     2500,
4✔
2554
                        valid:       false, // No outputs.
4✔
2555
                        unconfirmed: false,
4✔
2556
                },
4✔
2557
                {
4✔
2558
                        outVals:     []int64{},
4✔
2559
                        feeRate:     2500,
4✔
2560
                        valid:       false, // No outputs.
4✔
2561
                        unconfirmed: true,
4✔
2562
                },
4✔
2563

4✔
2564
                {
4✔
2565
                        outVals:     []int64{200},
4✔
2566
                        feeRate:     2500,
4✔
2567
                        valid:       false, // Dust output.
4✔
2568
                        unconfirmed: false,
4✔
2569
                },
4✔
2570
                {
4✔
2571
                        outVals:     []int64{200},
4✔
2572
                        feeRate:     2500,
4✔
2573
                        valid:       false, // Dust output.
4✔
2574
                        unconfirmed: true,
4✔
2575
                },
4✔
2576

4✔
2577
                {
4✔
2578
                        outVals:     []int64{1e8},
4✔
2579
                        feeRate:     2500,
4✔
2580
                        valid:       true,
4✔
2581
                        unconfirmed: false,
4✔
2582
                },
4✔
2583
                {
4✔
2584
                        outVals:     []int64{1e8},
4✔
2585
                        feeRate:     2500,
4✔
2586
                        valid:       true,
4✔
2587
                        unconfirmed: true,
4✔
2588
                },
4✔
2589

4✔
2590
                {
4✔
2591
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2592
                        feeRate:     2500,
4✔
2593
                        valid:       true,
4✔
2594
                        unconfirmed: false,
4✔
2595
                },
4✔
2596
                {
4✔
2597
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2598
                        feeRate:     2500,
4✔
2599
                        valid:       true,
4✔
2600
                        unconfirmed: true,
4✔
2601
                },
4✔
2602

4✔
2603
                {
4✔
2604
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2605
                        feeRate:     12500,
4✔
2606
                        valid:       true,
4✔
2607
                        unconfirmed: false,
4✔
2608
                },
4✔
2609
                {
4✔
2610
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2611
                        feeRate:     12500,
4✔
2612
                        valid:       true,
4✔
2613
                        unconfirmed: true,
4✔
2614
                },
4✔
2615

4✔
2616
                {
4✔
2617
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2618
                        feeRate:     50000,
4✔
2619
                        valid:       true,
4✔
2620
                        unconfirmed: false,
4✔
2621
                },
4✔
2622
                {
4✔
2623
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2624
                        feeRate:     50000,
4✔
2625
                        valid:       true,
4✔
2626
                        unconfirmed: true,
4✔
2627
                },
4✔
2628

4✔
2629
                {
4✔
2630
                        outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
4✔
2631
                                1e8, 2e7, 3e5},
4✔
2632
                        feeRate:     44250,
4✔
2633
                        valid:       true,
4✔
2634
                        unconfirmed: false,
4✔
2635
                },
4✔
2636
                {
4✔
2637
                        outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
4✔
2638
                                1e8, 2e7, 3e5},
4✔
2639
                        feeRate:     44250,
4✔
2640
                        valid:       true,
4✔
2641
                        unconfirmed: true,
4✔
2642
                },
4✔
2643
        }
4✔
2644

4✔
2645
        for i, test := range testCases {
60✔
2646
                var minConfs int32 = 1
56✔
2647

56✔
2648
                feeRate := test.feeRate
56✔
2649

56✔
2650
                // Grab some fresh addresses from the miner that we will send
56✔
2651
                // to.
56✔
2652
                outputs := make([]*wire.TxOut, len(test.outVals))
56✔
2653
                for i, outVal := range test.outVals {
272✔
2654
                        minerAddr, err := r.NewAddress()
216✔
2655
                        if err != nil {
216✔
2656
                                t.Fatalf("unable to generate address for "+
×
2657
                                        "miner: %v", err)
×
2658
                        }
×
2659
                        script, err := txscript.PayToAddrScript(minerAddr)
216✔
2660
                        if err != nil {
216✔
2661
                                t.Fatalf("unable to create pay to addr "+
×
2662
                                        "script: %v", err)
×
2663
                        }
×
2664
                        output := &wire.TxOut{
216✔
2665
                                Value:    outVal,
216✔
2666
                                PkScript: script,
216✔
2667
                        }
216✔
2668

216✔
2669
                        outputs[i] = output
216✔
2670
                }
2671

2672
                // Now try creating a tx spending to these outputs.
2673
                createTx, createErr := w.CreateSimpleTx(
56✔
2674
                        nil, outputs, feeRate, minConfs,
56✔
2675
                        w.Cfg.CoinSelectionStrategy, true,
56✔
2676
                )
56✔
2677
                switch {
56✔
2678
                case test.valid && createErr != nil:
×
2679
                        fmt.Println(spew.Sdump(createTx.Tx))
×
2680
                        t.Fatalf("got unexpected error when creating tx: %v",
×
2681
                                createErr)
×
2682

2683
                case !test.valid && createErr == nil:
×
2684
                        t.Fatalf("test #%v should have failed on tx "+
×
2685
                                "creation", i)
×
2686
                }
2687

2688
                // Also send to these outputs. This should result in a tx
2689
                // _very_ similar to the one we just created being sent. The
2690
                // only difference is that the dry run tx is not signed, and
2691
                // that the change output position might be different.
2692
                tx, sendErr := w.SendOutputs(
56✔
2693
                        nil, outputs, feeRate, minConfs, labels.External,
56✔
2694
                        w.Cfg.CoinSelectionStrategy,
56✔
2695
                )
56✔
2696
                switch {
56✔
2697
                case test.valid && sendErr != nil:
×
2698
                        t.Fatalf("got unexpected error when sending tx: %v",
×
2699
                                sendErr)
×
2700

2701
                case !test.valid && sendErr == nil:
×
2702
                        t.Fatalf("test #%v should fail for tx sending", i)
×
2703
                }
2704

2705
                // We expected either both to not fail, or both to fail with
2706
                // the same error.
2707
                if createErr != sendErr {
56✔
2708
                        t.Fatalf("error creating tx (%v) different "+
×
2709
                                "from error sending outputs (%v)",
×
2710
                                createErr, sendErr)
×
2711
                }
×
2712

2713
                // If we expected the creation to fail, then this test is over.
2714
                if !test.valid {
72✔
2715
                        continue
16✔
2716
                }
2717

2718
                txid := tx.TxHash()
40✔
2719
                err = waitForMempoolTx(r, &txid)
40✔
2720
                if err != nil {
40✔
2721
                        t.Fatalf("tx not relayed to miner: %v", err)
×
2722
                }
×
2723

2724
                // Helper method to check that the two txs are similar.
2725
                assertSimilarTx := func(a, b *wire.MsgTx) error {
80✔
2726
                        if a.Version != b.Version {
40✔
2727
                                return fmt.Errorf("different versions: "+
×
2728
                                        "%v vs %v", a.Version, b.Version)
×
2729
                        }
×
2730
                        if a.LockTime != b.LockTime {
40✔
2731
                                return fmt.Errorf("different locktimes: "+
×
2732
                                        "%v vs %v", a.LockTime, b.LockTime)
×
2733
                        }
×
2734
                        if len(a.TxIn) != len(b.TxIn) {
40✔
2735
                                return fmt.Errorf("different number of "+
×
2736
                                        "inputs: %v vs %v", len(a.TxIn),
×
2737
                                        len(b.TxIn))
×
2738
                        }
×
2739
                        if len(a.TxOut) != len(b.TxOut) {
40✔
2740
                                return fmt.Errorf("different number of "+
×
2741
                                        "outputs: %v vs %v", len(a.TxOut),
×
2742
                                        len(b.TxOut))
×
2743
                        }
×
2744

2745
                        // They should be spending the same inputs.
2746
                        for i := range a.TxIn {
120✔
2747
                                prevA := a.TxIn[i].PreviousOutPoint
80✔
2748
                                prevB := b.TxIn[i].PreviousOutPoint
80✔
2749
                                if prevA != prevB {
80✔
2750
                                        return fmt.Errorf("different inputs: "+
×
2751
                                                "%v vs %v", spew.Sdump(prevA),
×
2752
                                                spew.Sdump(prevB))
×
2753
                                }
×
2754
                        }
2755

2756
                        // They should have the same outputs. Since the change
2757
                        // output position gets randomized, they are not
2758
                        // guaranteed to be in the same order.
2759
                        for _, outA := range a.TxOut {
288✔
2760
                                found := false
248✔
2761
                                for _, outB := range b.TxOut {
1,304✔
2762
                                        if reflect.DeepEqual(outA, outB) {
1,304✔
2763
                                                found = true
248✔
2764
                                                break
248✔
2765
                                        }
2766
                                }
2767
                                if !found {
248✔
2768
                                        return fmt.Errorf("did not find "+
×
2769
                                                "output %v", spew.Sdump(outA))
×
2770
                                }
×
2771
                        }
2772
                        return nil
40✔
2773
                }
2774

2775
                // Assert that our "template tx" was similar to the one that
2776
                // ended up being sent.
2777
                if err := assertSimilarTx(createTx.Tx, tx); err != nil {
40✔
2778
                        t.Fatalf("transactions not similar: %v", err)
×
2779
                }
×
2780

2781
                // Now that we know both transactions were essentially
2782
                // identical, we'll make sure that a P2TR addr was used as the
2783
                // change output, which is the current default.
2784
                changeTxOut := createTx.Tx.TxOut[createTx.ChangeIndex]
40✔
2785
                changeScriptType, _, _, err := txscript.ExtractPkScriptAddrs(
40✔
2786
                        changeTxOut.PkScript, &w.Cfg.NetParams,
40✔
2787
                )
40✔
2788
                require.NoError(t, err)
40✔
2789
                require.Equal(t, changeScriptType, txscript.WitnessV1TaprootTy)
40✔
2790
        }
2791
}
2792

2793
// testSignOutputCreateAccount tests that we're able to properly sign for an
2794
// output if the target account hasn't yet been created on disk. In this case,
2795
// we'll create the account, then sign.
2796
func testSignOutputCreateAccount(r *rpctest.Harness, w *lnwallet.LightningWallet,
2797
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2798

4✔
2799
        // First, we'll create a sign desc that references a non-default key
4✔
2800
        // family. Under the hood, key families are actually accounts, so this
4✔
2801
        // should force create of the account so we can sign with it.
4✔
2802
        fakeTx := wire.NewMsgTx(2)
4✔
2803
        fakeTx.AddTxIn(&wire.TxIn{
4✔
2804
                PreviousOutPoint: wire.OutPoint{
4✔
2805
                        Hash:  chainhash.Hash{},
4✔
2806
                        Index: 0,
4✔
2807
                },
4✔
2808
        })
4✔
2809
        signDesc := &input.SignDescriptor{
4✔
2810
                KeyDesc: keychain.KeyDescriptor{
4✔
2811
                        KeyLocator: keychain.KeyLocator{
4✔
2812
                                Family: 99,
4✔
2813
                                Index:  1,
4✔
2814
                        },
4✔
2815
                },
4✔
2816
                WitnessScript: []byte{},
4✔
2817
                Output: &wire.TxOut{
4✔
2818
                        Value: 1000,
4✔
2819
                },
4✔
2820
                HashType:   txscript.SigHashAll,
4✔
2821
                SigHashes:  input.NewTxSigHashesV0Only(fakeTx),
4✔
2822
                InputIndex: 0,
4✔
2823
        }
4✔
2824

4✔
2825
        // We'll now sign and expect this to succeed, as even though the
4✔
2826
        // account doesn't exist atm, it should be created in order to process
4✔
2827
        // the inbound signing request.
4✔
2828
        _, err := w.Cfg.Signer.SignOutputRaw(fakeTx, signDesc)
4✔
2829
        if err != nil {
4✔
2830
                t.Fatalf("unable to sign for output with non-existent "+
×
2831
                        "account: %v", err)
×
2832
        }
×
2833
}
2834

2835
type walletTestCase struct {
2836
        name string
2837
        test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
2838
                test *testing.T)
2839
}
2840

2841
var walletTests = []walletTestCase{
2842
        {
2843
                // TODO(wilmer): this test should remain first until the wallet
2844
                // can properly craft a transaction that spends all of its
2845
                // on-chain funds.
2846
                name: "change output spend confirmation",
2847
                test: testChangeOutputSpendConfirmation,
2848
        },
2849
        {
2850
                // TODO(guggero): this test should remain second until dual
2851
                // funding can properly exchange full UTXO information and we
2852
                // can use P2TR change outputs as the funding inputs for a dual
2853
                // funded channel.
2854
                name: "dual funder workflow",
2855
                test: testDualFundingReservationWorkflow,
2856
        },
2857
        {
2858
                name: "spend unconfirmed outputs",
2859
                test: testSpendUnconfirmed,
2860
        },
2861
        {
2862
                name: "insane fee reject",
2863
                test: testReservationInitiatorBalanceBelowDustCancel,
2864
        },
2865
        {
2866
                name: "single funding workflow",
2867
                test: func(miner *rpctest.Harness, alice,
2868
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2869

4✔
2870
                        testSingleFunderReservationWorkflow(
4✔
2871
                                miner, alice, bob, t,
4✔
2872
                                lnwallet.CommitmentTypeLegacy, nil,
4✔
2873
                                nil, [32]byte{1}, 0,
4✔
2874
                        )
4✔
2875
                },
4✔
2876
        },
2877
        {
2878
                name: "single funding workflow tweakless",
2879
                test: func(miner *rpctest.Harness, alice,
2880
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2881

4✔
2882
                        testSingleFunderReservationWorkflow(
4✔
2883
                                miner, alice, bob, t,
4✔
2884
                                lnwallet.CommitmentTypeTweakless, nil,
4✔
2885
                                nil, [32]byte{1}, 0,
4✔
2886
                        )
4✔
2887
                },
4✔
2888
        },
2889
        {
2890
                name: "single funding workflow musig2",
2891
                test: func(miner *rpctest.Harness, alice,
2892
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2893

4✔
2894
                        testSingleFunderReservationWorkflow(
4✔
2895
                                miner, alice, bob, t,
4✔
2896
                                lnwallet.CommitmentTypeSimpleTaproot, nil,
4✔
2897
                                nil, [32]byte{1}, 0,
4✔
2898
                        )
4✔
2899
                },
4✔
2900
        },
2901
        // TODO(roasbeef): add musig2 external funding
2902
        {
2903
                name: "single funding workflow external funding tx",
2904
                test: testSingleFunderExternalFundingTx,
2905
        },
2906
        {
2907
                name: "output locking",
2908
                test: testFundingTransactionLockedOutputs,
2909
        },
2910
        {
2911
                name: "reservation insufficient funds",
2912
                test: testFundingCancellationNotEnoughFunds,
2913
        },
2914
        {
2915
                name: "transaction subscriptions",
2916
                test: testTransactionSubscriptions,
2917
        },
2918
        {
2919
                name: "transaction details",
2920
                test: testListTransactionDetails,
2921
        },
2922
        {
2923
                name: "transaction details offset",
2924
                test: testListTransactionDetailsOffset,
2925
        },
2926
        {
2927
                name: "get transaction details",
2928
                test: testGetTransactionDetails,
2929
        },
2930
        {
2931
                name: "publish transaction",
2932
                test: testPublishTransaction,
2933
        },
2934
        {
2935
                name: "signed with tweaked pubkeys",
2936
                test: testSignOutputUsingTweaks,
2937
        },
2938
        {
2939
                name: "test cancel non-existent reservation",
2940
                test: testCancelNonExistentReservation,
2941
        },
2942
        {
2943
                name: "last unused addr",
2944
                test: testLastUnusedAddr,
2945
        },
2946
        {
2947
                name: "reorg wallet balance",
2948
                test: testReorgWalletBalance,
2949
        },
2950
        {
2951
                name: "create simple tx",
2952
                test: testCreateSimpleTx,
2953
        },
2954
        {
2955
                name: "test sign create account",
2956
                test: testSignOutputCreateAccount,
2957
        },
2958
        {
2959
                name: "test get recovery info",
2960
                test: testGetRecoveryInfo,
2961
        },
2962
}
2963

2964
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
88✔
2965
        a.ResetReservations()
88✔
2966
        b.ResetReservations()
88✔
2967

88✔
2968
        if err := a.Cfg.Database.GetParentDB().Wipe(); err != nil {
88✔
2969
                return err
×
2970
        }
×
2971

2972
        return b.Cfg.Database.GetParentDB().Wipe()
88✔
2973
}
2974

2975
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
157✔
2976
        var found bool
157✔
2977
        var tx *btcutil.Tx
157✔
2978
        var err error
157✔
2979
        timeout := time.After(30 * time.Second)
157✔
2980
        for !found {
1,725✔
2981
                // Do a short wait
1,568✔
2982
                select {
1,568✔
2983
                case <-timeout:
×
2984
                        return fmt.Errorf("timeout after 10s")
×
2985
                default:
1,568✔
2986
                }
2987
                time.Sleep(100 * time.Millisecond)
1,568✔
2988

1,568✔
2989
                // Check for the harness' knowledge of the txid
1,568✔
2990
                tx, err = r.Client.GetRawTransaction(txid)
1,568✔
2991
                if err != nil {
2,979✔
2992
                        switch e := err.(type) {
1,411✔
2993
                        case *btcjson.RPCError:
1,411✔
2994
                                if e.Code == btcjson.ErrRPCNoTxInfo {
2,822✔
2995
                                        continue
1,411✔
2996
                                }
2997
                        default:
×
2998
                        }
2999
                        return err
×
3000
                }
3001
                if tx != nil && tx.MsgTx().TxHash() == *txid {
314✔
3002
                        found = true
157✔
3003
                }
157✔
3004
        }
3005
        return nil
157✔
3006
}
3007

3008
func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
142✔
3009
        var (
142✔
3010
                synced                  bool
142✔
3011
                err                     error
142✔
3012
                bestHash, knownHash     *chainhash.Hash
142✔
3013
                bestHeight, knownHeight int32
142✔
3014
        )
142✔
3015
        timeout := time.After(10 * time.Second)
142✔
3016
        for !synced {
356✔
3017
                // Do a short wait
214✔
3018
                select {
214✔
3019
                case <-timeout:
×
3020
                        return fmt.Errorf("timeout after 30s")
×
3021
                case <-time.Tick(100 * time.Millisecond):
214✔
3022
                }
3023

3024
                // Check whether the chain source of the wallet is caught up to
3025
                // the harness it's supposed to be catching up to.
3026
                bestHash, bestHeight, err = r.Client.GetBestBlock()
214✔
3027
                if err != nil {
214✔
3028
                        return err
×
3029
                }
×
3030
                knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
214✔
3031
                if err != nil {
214✔
3032
                        return err
×
3033
                }
×
3034
                if knownHeight != bestHeight {
214✔
UNCOV
3035
                        continue
×
3036
                }
3037
                if *knownHash != *bestHash {
214✔
3038
                        return fmt.Errorf("hash at height %d doesn't match: "+
×
3039
                                "expected %s, got %s", bestHeight, bestHash,
×
3040
                                knownHash)
×
3041
                }
×
3042

3043
                // Check for synchronization.
3044
                synced, _, err = w.IsSynced()
214✔
3045
                if err != nil {
214✔
3046
                        return err
×
3047
                }
×
3048
        }
3049
        return nil
142✔
3050
}
3051

3052
// testSingleFunderExternalFundingTx tests that the wallet is able to properly
3053
// carry out a funding flow backed by a channel point that has been crafted
3054
// outside the wallet.
3055
func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
3056
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
3057

4✔
3058
        // Define a filter function without any restrictions.
4✔
3059
        allowUtxo := func(lnwallet.Utxo) bool {
77✔
3060
                return true
73✔
3061
        }
73✔
3062

3063
        // First, we'll obtain multi-sig keys from both Alice and Bob which
3064
        // simulates them exchanging keys on a higher level.
3065
        aliceFundingKey, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
4✔
3066
        require.NoError(t, err, "unable to obtain alice funding key")
4✔
3067
        bobFundingKey, err := bob.DeriveNextKey(keychain.KeyFamilyMultiSig)
4✔
3068
        require.NoError(t, err, "unable to obtain bob funding key")
4✔
3069

4✔
3070
        // We'll now set up for them to open a 4 BTC channel, with 1 BTC pushed
4✔
3071
        // to Bob's side.
4✔
3072
        chanAmt := 4 * btcutil.SatoshiPerBitcoin
4✔
3073

4✔
3074
        // Simulating external funding negotiation, we'll now create the
4✔
3075
        // funding transaction for both parties. Utilizing existing tools,
4✔
3076
        // we'll create a new chanfunding.Assembler hacked by Alice's wallet.
4✔
3077
        aliceChanFunder := chanfunding.NewWalletAssembler(
4✔
3078
                chanfunding.WalletConfig{
4✔
3079
                        CoinSource: lnwallet.NewCoinSource(
4✔
3080
                                alice, allowUtxo,
4✔
3081
                        ),
4✔
3082
                        CoinSelectLocker:      alice,
4✔
3083
                        CoinLeaser:            alice,
4✔
3084
                        Signer:                alice.Cfg.Signer,
4✔
3085
                        DustLimit:             600,
4✔
3086
                        CoinSelectionStrategy: wallet.CoinSelectionLargest,
4✔
3087
                },
4✔
3088
        )
4✔
3089

4✔
3090
        // With the chan funder created, we'll now provision a funding intent,
4✔
3091
        // bind the keys we obtained above, and finally obtain our funding
4✔
3092
        // transaction and outpoint.
4✔
3093
        fundingIntent, err := aliceChanFunder.ProvisionChannel(
4✔
3094
                &chanfunding.Request{
4✔
3095
                        LocalAmt: btcutil.Amount(chanAmt),
4✔
3096
                        MinConfs: 1,
4✔
3097
                        FeeRate:  253,
4✔
3098
                        ChangeAddr: func() (btcutil.Address, error) {
8✔
3099
                                return alice.NewAddress(
4✔
3100
                                        lnwallet.WitnessPubKey, true,
4✔
3101
                                        lnwallet.DefaultAccountName,
4✔
3102
                                )
4✔
3103
                        },
4✔
3104
                },
3105
        )
3106
        require.NoError(t, err, "unable to perform coin selection")
4✔
3107

4✔
3108
        // With our intent created, we'll instruct it to finalize the funding
4✔
3109
        // transaction, and also hand us the outpoint so we can simulate
4✔
3110
        // external crafting of the funding transaction.
4✔
3111
        var (
4✔
3112
                fundingTx *wire.MsgTx
4✔
3113
                chanPoint *wire.OutPoint
4✔
3114
        )
4✔
3115
        if fullIntent, ok := fundingIntent.(*chanfunding.FullIntent); ok {
8✔
3116
                fullIntent.BindKeys(&aliceFundingKey, bobFundingKey.PubKey)
4✔
3117

4✔
3118
                fundingTx, err = fullIntent.CompileFundingTx(nil, nil)
4✔
3119
                if err != nil {
4✔
3120
                        t.Fatalf("unable to compile funding tx: %v", err)
×
3121
                }
×
3122
                chanPoint, err = fullIntent.ChanPoint()
4✔
3123
                if err != nil {
4✔
3124
                        t.Fatalf("unable to obtain chan point: %v", err)
×
3125
                }
×
3126
        } else {
×
3127
                t.Fatalf("expected full intent, instead got: %T", fullIntent)
×
3128
        }
×
3129

3130
        // Now that we have the fully constructed funding transaction, we'll
3131
        // create a new shim external funder out of it for Alice, and prep a
3132
        // shim intent for Bob.
3133
        thawHeight := uint32(200)
4✔
3134
        aliceExternalFunder := chanfunding.NewCannedAssembler(
4✔
3135
                thawHeight, *chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey,
4✔
3136
                bobFundingKey.PubKey, true, false,
4✔
3137
        )
4✔
3138
        bobShimIntent, err := chanfunding.NewCannedAssembler(
4✔
3139
                thawHeight, *chanPoint, btcutil.Amount(chanAmt), &bobFundingKey,
4✔
3140
                aliceFundingKey.PubKey, false, false,
4✔
3141
        ).ProvisionChannel(&chanfunding.Request{
4✔
3142
                LocalAmt: btcutil.Amount(chanAmt),
4✔
3143
                MinConfs: 1,
4✔
3144
                FeeRate:  253,
4✔
3145
                ChangeAddr: func() (btcutil.Address, error) {
4✔
3146
                        return bob.NewAddress(
×
3147
                                lnwallet.WitnessPubKey, true,
×
3148
                                lnwallet.DefaultAccountName,
×
3149
                        )
×
3150
                },
×
3151
        })
3152
        require.NoError(t, err, "unable to create shim intent for bob")
4✔
3153

4✔
3154
        // At this point, we have everything we need to carry out our test, so
4✔
3155
        // we'll being the funding flow between Alice and Bob.
4✔
3156
        //
4✔
3157
        // However, before we do so, we'll register a new shim intent for Bob,
4✔
3158
        // so he knows what keys to use when he receives the funding request
4✔
3159
        // from Alice.
4✔
3160
        pendingChanID := testHdSeed
4✔
3161
        err = bob.RegisterFundingIntent(pendingChanID, bobShimIntent)
4✔
3162
        require.NoError(t, err, "unable to register intent")
4✔
3163

4✔
3164
        // Now we can carry out the single funding flow as normal, we'll
4✔
3165
        // specify our external funder and funding transaction, as well as the
4✔
3166
        // pending channel ID generated above to allow Alice and Bob to track
4✔
3167
        // the funding flow externally.
4✔
3168
        testSingleFunderReservationWorkflow(
4✔
3169
                miner, alice, bob, t, lnwallet.CommitmentTypeTweakless,
4✔
3170
                aliceExternalFunder, func() *wire.MsgTx {
8✔
3171
                        return fundingTx
4✔
3172
                }, pendingChanID, thawHeight,
4✔
3173
        )
3174
}
3175

3176
// TestLightningWallet tests all registered interfaces with a unified set of
3177
// tests which exercise each of the required methods found within the
3178
// WalletController interface.
3179
//
3180
// NOTE: In the future, when additional implementations of the WalletController
3181
// interface have been implemented, in order to ensure the new concrete
3182
// implementation is automatically tested, two steps must be undertaken. First,
3183
// one needs add a "non-captured" (_) import from the new sub-package. This
3184
// import should trigger an init() method within the package which registers
3185
// the interface. Second, an additional case in the switch within the main loop
3186
// below needs to be added which properly initializes the interface.
3187
//
3188
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
3189
func TestLightningWallet(t *testing.T, targetBackEnd string) {
4✔
3190
        t.Parallel()
4✔
3191

4✔
3192
        // Initialize the harness around a btcd node which will serve as our
4✔
3193
        // dedicated miner to generate blocks, cause re-orgs, etc. We'll set
4✔
3194
        // up this node with a chain length of 125, so we have plenty of BTC
4✔
3195
        // to play around with.
4✔
3196
        miningNode := unittest.NewMiner(
4✔
3197
                t, netParams, []string{"--txindex"}, true, 25,
4✔
3198
        )
4✔
3199

4✔
3200
        // Next mine enough blocks in order for segwit and the CSV package
4✔
3201
        // soft-fork to activate on RegNet.
4✔
3202
        numBlocks := netParams.MinerConfirmationWindow * 2
4✔
3203
        if _, err := miningNode.Client.Generate(numBlocks); err != nil {
4✔
3204
                t.Fatalf("unable to generate blocks: %v", err)
×
3205
        }
×
3206

3207
        rpcConfig := miningNode.RPCConfig()
4✔
3208

4✔
3209
        tempDir := t.TempDir()
4✔
3210
        db, err := channeldb.Open(tempDir)
4✔
3211
        require.NoError(t, err, "unable to create db")
4✔
3212
        testCfg := channeldb.CacheConfig{
4✔
3213
                QueryDisable: false,
4✔
3214
        }
4✔
3215
        hintCache, err := channeldb.NewHeightHintCache(testCfg, db.Backend)
4✔
3216
        require.NoError(t, err, "unable to create height hint cache")
4✔
3217
        blockCache := blockcache.NewBlockCache(10000)
4✔
3218
        chainNotifier, err := btcdnotify.New(
4✔
3219
                &rpcConfig, netParams, hintCache, hintCache, blockCache,
4✔
3220
        )
4✔
3221
        require.NoError(t, err, "unable to create notifier")
4✔
3222
        if err := chainNotifier.Start(); err != nil {
4✔
3223
                t.Fatalf("unable to start notifier: %v", err)
×
3224
        }
×
3225

3226
        for _, walletDriver := range lnwallet.RegisteredWallets() {
8✔
3227
                for _, backEnd := range walletDriver.BackEnds() {
20✔
3228
                        if backEnd != targetBackEnd {
28✔
3229
                                continue
12✔
3230
                        }
3231

3232
                        if !runTests(t, walletDriver, backEnd, miningNode,
4✔
3233
                                rpcConfig, chainNotifier) {
4✔
3234

×
3235
                                return
×
3236
                        }
×
3237
                }
3238
        }
3239
}
3240

3241
// runTests runs all of the tests for a single interface implementation and
3242
// chain back-end combination. This makes it easier to use `defer` as well as
3243
// factoring out the test logic from the loop which cycles through the
3244
// interface implementations.
3245
func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
3246
        backEnd string, miningNode *rpctest.Harness,
3247
        rpcConfig rpcclient.ConnConfig,
3248
        chainNotifier chainntnfs.ChainNotifier) bool {
4✔
3249

4✔
3250
        var (
4✔
3251
                bio lnwallet.BlockChainIO
4✔
3252

4✔
3253
                aliceSigner input.Signer
4✔
3254
                bobSigner   input.Signer
4✔
3255

4✔
3256
                aliceKeyRing keychain.SecretKeyRing
4✔
3257
                bobKeyRing   keychain.SecretKeyRing
4✔
3258

4✔
3259
                aliceWalletController lnwallet.WalletController
4✔
3260
                bobWalletController   lnwallet.WalletController
4✔
3261

4✔
3262
                err error
4✔
3263
        )
4✔
3264

4✔
3265
        tempTestDirAlice := t.TempDir()
4✔
3266
        tempTestDirBob := t.TempDir()
4✔
3267

4✔
3268
        blockCache := blockcache.NewBlockCache(10000)
4✔
3269

4✔
3270
        walletType := walletDriver.WalletType
4✔
3271
        switch walletType {
4✔
3272
        case "btcwallet":
4✔
3273
                var aliceClient, bobClient chain.Interface
4✔
3274
                switch backEnd {
4✔
3275
                case "btcd":
1✔
3276
                        aliceClient, err = chain.NewRPCClient(
1✔
3277
                                netParams, rpcConfig.Host, rpcConfig.User,
1✔
3278
                                rpcConfig.Pass, rpcConfig.Certificates, false,
1✔
3279
                                20,
1✔
3280
                        )
1✔
3281
                        if err != nil {
1✔
3282
                                t.Fatalf("unable to make chain rpc: %v", err)
×
3283
                        }
×
3284
                        bobClient, err = chain.NewRPCClient(
1✔
3285
                                netParams, rpcConfig.Host, rpcConfig.User,
1✔
3286
                                rpcConfig.Pass, rpcConfig.Certificates, false,
1✔
3287
                                20,
1✔
3288
                        )
1✔
3289
                        if err != nil {
1✔
3290
                                t.Fatalf("unable to make chain rpc: %v", err)
×
3291
                        }
×
3292

3293
                case "neutrino":
1✔
3294
                        // Set some package-level variable to speed up
1✔
3295
                        // operation for tests.
1✔
3296
                        neutrino.BanDuration = time.Millisecond * 100
1✔
3297
                        neutrino.QueryTimeout = time.Millisecond * 500
1✔
3298
                        neutrino.QueryNumRetries = 1
1✔
3299

1✔
3300
                        // Start Alice - open a database, start a neutrino
1✔
3301
                        // instance, and initialize a btcwallet driver for it.
1✔
3302
                        aliceDB, err := walletdb.Create(
1✔
3303
                                "bdb", tempTestDirAlice+"/neutrino.db", true,
1✔
3304
                                kvdb.DefaultDBTimeout,
1✔
3305
                        )
1✔
3306
                        if err != nil {
1✔
3307
                                t.Fatalf("unable to create DB: %v", err)
×
3308
                        }
×
3309
                        defer aliceDB.Close()
1✔
3310
                        aliceChain, err := neutrino.NewChainService(
1✔
3311
                                neutrino.Config{
1✔
3312
                                        DataDir:     tempTestDirAlice,
1✔
3313
                                        Database:    aliceDB,
1✔
3314
                                        ChainParams: *netParams,
1✔
3315
                                        ConnectPeers: []string{
1✔
3316
                                                miningNode.P2PAddress(),
1✔
3317
                                        },
1✔
3318
                                },
1✔
3319
                        )
1✔
3320
                        if err != nil {
1✔
3321
                                t.Fatalf("unable to make neutrino: %v", err)
×
3322
                        }
×
3323
                        aliceChain.Start()
1✔
3324
                        defer aliceChain.Stop()
1✔
3325
                        aliceClient = chain.NewNeutrinoClient(
1✔
3326
                                netParams, aliceChain,
1✔
3327
                        )
1✔
3328

1✔
3329
                        // Start Bob - open a database, start a neutrino
1✔
3330
                        // instance, and initialize a btcwallet driver for it.
1✔
3331
                        bobDB, err := walletdb.Create(
1✔
3332
                                "bdb", tempTestDirBob+"/neutrino.db", true,
1✔
3333
                                kvdb.DefaultDBTimeout,
1✔
3334
                        )
1✔
3335
                        if err != nil {
1✔
3336
                                t.Fatalf("unable to create DB: %v", err)
×
3337
                        }
×
3338
                        defer bobDB.Close()
1✔
3339
                        bobChain, err := neutrino.NewChainService(
1✔
3340
                                neutrino.Config{
1✔
3341
                                        DataDir:     tempTestDirBob,
1✔
3342
                                        Database:    bobDB,
1✔
3343
                                        ChainParams: *netParams,
1✔
3344
                                        ConnectPeers: []string{
1✔
3345
                                                miningNode.P2PAddress(),
1✔
3346
                                        },
1✔
3347
                                },
1✔
3348
                        )
1✔
3349
                        if err != nil {
1✔
3350
                                t.Fatalf("unable to make neutrino: %v", err)
×
3351
                        }
×
3352
                        bobChain.Start()
1✔
3353
                        defer bobChain.Stop()
1✔
3354
                        bobClient = chain.NewNeutrinoClient(
1✔
3355
                                netParams, bobChain,
1✔
3356
                        )
1✔
3357

3358
                case "bitcoind":
1✔
3359
                        // Start a bitcoind instance.
1✔
3360
                        chainConn := unittest.NewBitcoindBackend(
1✔
3361
                                t, unittest.NetParams, miningNode.P2PAddress(),
1✔
3362
                                true, false,
1✔
3363
                        )
1✔
3364

1✔
3365
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3366
                        // Bob.
1✔
3367
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3368
                        bobClient = chainConn.NewBitcoindClient()
1✔
3369

3370
                case "bitcoind-rpc-polling":
1✔
3371
                        // Start a bitcoind instance.
1✔
3372
                        chainConn := unittest.NewBitcoindBackend(
1✔
3373
                                t, unittest.NetParams, miningNode.P2PAddress(),
1✔
3374
                                true, true,
1✔
3375
                        )
1✔
3376

1✔
3377
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3378
                        // Bob.
1✔
3379
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3380
                        bobClient = chainConn.NewBitcoindClient()
1✔
3381

3382
                default:
×
3383
                        t.Fatalf("unknown chain driver: %v", backEnd)
×
3384
                }
3385

3386
                aliceSeed := sha256.New()
4✔
3387
                aliceSeed.Write([]byte(backEnd))
4✔
3388
                aliceSeed.Write(aliceHDSeed[:])
4✔
3389
                aliceSeedBytes := aliceSeed.Sum(nil)
4✔
3390

4✔
3391
                aliceWalletConfig := &btcwallet.Config{
4✔
3392
                        PrivatePass: []byte("alice-pass"),
4✔
3393
                        HdSeed:      aliceSeedBytes,
4✔
3394
                        NetParams:   netParams,
4✔
3395
                        ChainSource: aliceClient,
4✔
3396
                        CoinType:    keychain.CoinTypeTestnet,
4✔
3397
                        // wallet starts in recovery mode
4✔
3398
                        RecoveryWindow: 2,
4✔
3399
                        LoaderOptions: []btcwallet.LoaderOption{
4✔
3400
                                btcwallet.LoaderWithLocalWalletDB(
4✔
3401
                                        tempTestDirAlice, false, time.Minute,
4✔
3402
                                ),
4✔
3403
                        },
4✔
3404
                }
4✔
3405
                aliceWalletController, err = walletDriver.New(
4✔
3406
                        aliceWalletConfig, blockCache,
4✔
3407
                )
4✔
3408
                if err != nil {
4✔
3409
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3410
                }
×
3411
                aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
4✔
3412
                aliceKeyRing = keychain.NewBtcWalletKeyRing(
4✔
3413
                        aliceWalletController.(*btcwallet.BtcWallet).InternalWallet(),
4✔
3414
                        keychain.CoinTypeTestnet,
4✔
3415
                )
4✔
3416

4✔
3417
                bobSeed := sha256.New()
4✔
3418
                bobSeed.Write([]byte(backEnd))
4✔
3419
                bobSeed.Write(bobHDSeed[:])
4✔
3420
                bobSeedBytes := bobSeed.Sum(nil)
4✔
3421

4✔
3422
                bobWalletConfig := &btcwallet.Config{
4✔
3423
                        PrivatePass: []byte("bob-pass"),
4✔
3424
                        HdSeed:      bobSeedBytes,
4✔
3425
                        NetParams:   netParams,
4✔
3426
                        ChainSource: bobClient,
4✔
3427
                        CoinType:    keychain.CoinTypeTestnet,
4✔
3428
                        // wallet starts without recovery mode
4✔
3429
                        RecoveryWindow: 0,
4✔
3430
                        LoaderOptions: []btcwallet.LoaderOption{
4✔
3431
                                btcwallet.LoaderWithLocalWalletDB(
4✔
3432
                                        tempTestDirBob, false, time.Minute,
4✔
3433
                                ),
4✔
3434
                        },
4✔
3435
                }
4✔
3436
                bobWalletController, err = walletDriver.New(
4✔
3437
                        bobWalletConfig, blockCache,
4✔
3438
                )
4✔
3439
                if err != nil {
4✔
3440
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3441
                }
×
3442
                bobSigner = bobWalletController.(*btcwallet.BtcWallet)
4✔
3443
                bobKeyRing = keychain.NewBtcWalletKeyRing(
4✔
3444
                        bobWalletController.(*btcwallet.BtcWallet).InternalWallet(),
4✔
3445
                        keychain.CoinTypeTestnet,
4✔
3446
                )
4✔
3447
                bio = bobWalletController.(*btcwallet.BtcWallet)
4✔
3448
        default:
×
3449
                t.Fatalf("unknown wallet driver: %v", walletType)
×
3450
        }
3451

3452
        // Funding via 20 outputs with 4BTC each.
3453
        alice, err := createTestWallet(
4✔
3454
                tempTestDirAlice, miningNode, netParams,
4✔
3455
                chainNotifier, aliceWalletController, aliceKeyRing,
4✔
3456
                aliceSigner, bio,
4✔
3457
        )
4✔
3458
        require.NoError(t, err, "unable to create test ln wallet")
4✔
3459
        defer alice.Shutdown()
4✔
3460

4✔
3461
        bob, err := createTestWallet(
4✔
3462
                tempTestDirBob, miningNode, netParams,
4✔
3463
                chainNotifier, bobWalletController, bobKeyRing, bobSigner, bio,
4✔
3464
        )
4✔
3465
        require.NoError(t, err, "unable to create test ln wallet")
4✔
3466
        defer bob.Shutdown()
4✔
3467

4✔
3468
        // Both wallets should now have 80BTC available for
4✔
3469
        // spending.
4✔
3470
        assertProperBalance(t, alice, 1, 80)
4✔
3471
        assertProperBalance(t, bob, 1, 80)
4✔
3472

4✔
3473
        // Execute every test, clearing possibly mutated
4✔
3474
        // wallet state after each step.
4✔
3475
        for _, walletTest := range walletTests {
92✔
3476

88✔
3477
                walletTest := walletTest
88✔
3478

88✔
3479
                testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
88✔
3480
                        walletTest.name)
88✔
3481
                success := t.Run(testName, func(t *testing.T) {
176✔
3482
                        if backEnd == "neutrino" &&
88✔
3483
                                strings.Contains(walletTest.name, "dual funder") {
89✔
3484

1✔
3485
                                t.Skip("skipping dual funder tests for neutrino")
1✔
3486
                        }
1✔
3487
                        if backEnd == "neutrino" &&
87✔
3488
                                strings.Contains(walletTest.name, "spend unconfirmed") {
88✔
3489

1✔
3490
                                t.Skip("skipping spend unconfirmed tests for neutrino")
1✔
3491
                        }
1✔
3492

3493
                        walletTest.test(miningNode, alice, bob, t)
86✔
3494
                })
3495
                if !success {
88✔
3496
                        return false
×
3497
                }
×
3498

3499
                // TODO(roasbeef): possible reset mining
3500
                // node's chainstate to initial level, cleanly
3501
                // wipe buckets
3502
                if err := clearWalletStates(alice, bob); err !=
88✔
3503
                        nil && err != kvdb.ErrBucketNotFound {
88✔
3504

×
3505
                        t.Fatalf("unable to wipe wallet state: %v", err)
×
3506
                }
×
3507
        }
3508

3509
        return true
4✔
3510
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc