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

lightningnetwork / lnd / 17078487729

19 Aug 2025 06:29PM UTC coverage: 66.758% (+0.08%) from 66.68%
17078487729

Pull #10167

github

web-flow
Merge cff678d8b into e25945658
Pull Request #10167: multi: bump Go to 1.24.6

6 of 17 new or added lines in 7 files covered. (35.29%)

71 existing lines in 17 files now uncovered.

135955 of 203652 relevant lines covered (66.76%)

21462.76 hits per line

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

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

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

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

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

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

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

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

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

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

91
        csvDelay uint16 = 4
92

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

96
        defaultMaxLocalCsvDelay uint16 = 10000
97
)
98

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

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

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

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

34✔
124
        t.Helper()
34✔
125

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

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

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

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

18✔
155
        t.Helper()
18✔
156

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

18✔
162
        return pkScript
18✔
163
}
18✔
164

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

40✔
171
        t.Helper()
40✔
172

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

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

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

190
        return tx
40✔
191
}
192

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

64✔
198
        t.Helper()
64✔
199

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

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

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

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

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

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

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

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

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

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

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

20✔
310
        return nil
20✔
311
}
312

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

8✔
321
        dbDir := filepath.Join(tempTestDir, "cdb")
8✔
322
        fullDB := channeldb.OpenForTesting(t, dbDir)
8✔
323

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

8✔
336
        wallet, err := lnwallet.NewLightningWallet(cfg)
8✔
337
        require.NoError(t, err)
8✔
338

8✔
339
        require.NoError(t, wallet.Startup())
8✔
340

8✔
341
        t.Cleanup(func() {
16✔
342
                require.NoError(t, wallet.Shutdown())
8✔
343
        })
8✔
344

345
        // Load our test wallet with 20 outputs each holding 4BTC.
346
        require.NoError(t, loadTestCredits(miningNode, wallet, 20, 4))
8✔
347

8✔
348
        return wallet
8✔
349
}
350

351
func testGetRecoveryInfo(miner *rpctest.Harness,
352
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
353

4✔
354
        // alice's wallet is in recovery mode
4✔
355
        expectedRecoveryMode := true
4✔
356
        expectedProgress := float64(1)
4✔
357

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

4✔
361
        require.Equal(t,
4✔
362
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
363
        )
4✔
364
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
365

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

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

4✔
375
        isRecoveryMode, progress, err = alice.GetRecoveryInfo()
4✔
376
        require.NoError(t, err, "unable to get alice's recovery info")
4✔
377

4✔
378
        require.Equal(t,
4✔
379
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
380
        )
4✔
381
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
382

4✔
383
        // bob's wallet is not in recovery mode
4✔
384
        expectedRecoveryMode = false
4✔
385
        expectedProgress = float64(0)
4✔
386

4✔
387
        isRecoveryMode, progress, err = bob.GetRecoveryInfo()
4✔
388
        require.NoError(t, err, "unable to get bob's recovery info")
4✔
389

4✔
390
        require.Equal(t,
4✔
391
                expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
4✔
392
        )
4✔
393
        require.Equal(t, expectedProgress, progress, "progress incorrect")
4✔
394
}
4✔
395

396
func testDualFundingReservationWorkflow(miner *rpctest.Harness,
397
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
3✔
398

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

3✔
401
        fundingAmount, err := btcutil.NewAmount(5)
3✔
402
        require.NoError(t, err, "unable to create amt")
3✔
403

3✔
404
        // In this scenario, we'll test a dual funder reservation, with each
3✔
405
        // side putting in 10 BTC.
3✔
406

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

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

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

×
473
        assertContributionInitPopulated(t, bobChanReservation.OurContribution())
×
474

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

×
479
        bobContribution := bobChanReservation.OurContribution()
×
480

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

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

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

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

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

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

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

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

569
        assertReservationDeleted(aliceChanReservation, t)
×
570
        assertReservationDeleted(bobChanReservation, t)
×
571

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

579
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
580
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
581

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

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

632
func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
633
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
634

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

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

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

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

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

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

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

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

693
func testCancelNonExistentReservation(miner *rpctest.Harness,
694
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
695

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

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

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

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

720
func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
721
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
722

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

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

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

757
func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContribution) {
64✔
758
        _, _, line, _ := runtime.Caller(1)
64✔
759

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

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

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

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

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

16✔
804
        // For this scenario, Alice will be the channel initiator while bob
16✔
805
        // will act as the responder to the workflow.
16✔
806

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1046
        assertReservationDeleted(aliceChanReservation, t)
16✔
1047
        assertReservationDeleted(bobChanReservation, t)
16✔
1048
}
1049

1050
func testListTransactionDetails(miner *rpctest.Harness,
1051
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1052

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

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

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

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

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

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

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

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

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

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

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

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

1150
                        blockTxOuts[fetchedBlock.BlockHash()] = transactions
4✔
1151
                }
1152

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

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

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

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

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

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

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

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

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

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

4✔
1259
        // Progress our chain tip by the number of blocks we have just mined.
4✔
1260
        chainTip += numBlocks
4✔
1261

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

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

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

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

4✔
1306
        err = waitForWalletSync(miner, alice)
4✔
1307
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1308

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

1320
func testListTransactionDetailsOffset(miner *rpctest.Harness,
1321
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1322

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

20✔
1335
                isOurAddress[addr.EncodeAddress()] = true
20✔
1336
                script, err := txscript.PayToAddrScript(addr)
20✔
1337
                require.NoError(t, err)
20✔
1338

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

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

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

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

4✔
1361
        err = waitForWalletSync(miner, alice)
4✔
1362
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1363

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

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

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

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

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

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

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

1421
func testTransactionSubscriptions(miner *rpctest.Harness,
1422
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1423

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

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

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

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

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

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

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

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

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

4✔
1557
        outputScript, err := txscript.PayToAddrScript(addr)
4✔
1558
        require.NoError(t, err)
4✔
1559

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

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

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

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

1603
        return keyScript, nil
66✔
1604
}
1605

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

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

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

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

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

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

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

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

1652
        // We assume the output was paid to the keyScript made earlier.
1653
        var outputIndex uint32
27✔
1654
        if len(tx.TxOut) == 1 || bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
50✔
1655
                outputIndex = 0
23✔
1656
        } else {
27✔
1657
                outputIndex = 1
4✔
1658
        }
4✔
1659
        outputValue := tx.TxOut[outputIndex].Value
27✔
1660

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

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

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

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

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

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

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

1731
        return tx1, nil
27✔
1732
}
1733

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

12✔
1739
        t.Helper()
12✔
1740

12✔
1741
        keyScript, err := scriptFromKey(pubKey)
12✔
1742
        require.NoError(t, err, "unable to generate script")
12✔
1743

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

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

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

1771
        return tx1
12✔
1772
}
1773

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

4✔
1779
        const txFee = int64(14500)
4✔
1780

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

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

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

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

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

4✔
1813
                // Publish the transaction.
4✔
1814
                err = alice.PublishTransaction(tx1, labels.External)
4✔
1815
                require.NoError(t, err)
4✔
1816

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

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

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

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

4✔
1842
                // Publish this tx.
4✔
1843
                err = alice.PublishTransaction(tx2, labels.External)
4✔
1844
                require.NoError(t, err)
4✔
1845

4✔
1846
                // Mine the transaction.
4✔
1847
                err := mineAndAssert(r, tx2)
4✔
1848
                require.NoError(t, err)
4✔
1849

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

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

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

4✔
1876
                // Mine the transaction.
4✔
1877
                err = mineAndAssert(r, tx3)
4✔
1878
                require.NoError(t, err)
4✔
1879

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

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

4✔
1892
                // Keep track of the last successfully published tx to
4✔
1893
                // spend tx3.
4✔
1894
                tx3Spend = tx4
4✔
1895

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

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

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

4✔
1915
                err = alice.PublishTransaction(tx5, labels.External)
4✔
1916

4✔
1917
                // We expect it to be rejected/ because it doesn't pay enough
4✔
1918
                // fees.
4✔
1919
                expectedErr := chain.ErrInsufficientFee
4✔
1920

4✔
1921
                // Assert the expected error.
4✔
1922
                require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf)
4✔
1923

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

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

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

4✔
1944
                // Mine the tx spending tx3.
4✔
1945
                err = mineAndAssert(r, tx3Spend)
4✔
1946
                require.NoError(t, err)
4✔
1947
        })
4✔
1948

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

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

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

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

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

1999
func testSignOutputUsingTweaks(r *rpctest.Harness,
2000
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2001

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

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

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

4✔
2021
        revocationKey := input.DeriveRevocationPubkey(pubKey.PubKey, commitPoint)
4✔
2022
        commitTweak := input.SingleTweakBytes(commitPoint, pubKey.PubKey)
4✔
2023

4✔
2024
        tweakedPub := input.TweakPubKey(pubKey.PubKey, commitPoint)
4✔
2025

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

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

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

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

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

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

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

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

2147
func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
2148
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2149

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3✔
2378
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
3✔
2379
        alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
3✔
2380
        txFeeRate := chainfee.SatPerKWeight(2500)
3✔
2381

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4✔
2642
        for i, test := range testCases {
60✔
2643
                var minConfs int32 = 1
56✔
2644

56✔
2645
                feeRate := test.feeRate
56✔
2646

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

216✔
2666
                        outputs[i] = output
216✔
2667
                }
2668

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2961
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
88✔
2962
        a.ResetReservations()
88✔
2963
        b.ResetReservations()
88✔
2964

88✔
2965
        if err := a.Cfg.Database.GetParentDB().Wipe(); err != nil {
88✔
2966
                return err
×
2967
        }
×
2968

2969
        return b.Cfg.Database.GetParentDB().Wipe()
88✔
2970
}
2971

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

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

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

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

3040
                // Check for synchronization.
3041
                synced, _, err = w.IsSynced()
339✔
3042
                if err != nil {
339✔
3043
                        return err
×
3044
                }
×
3045
        }
3046
        return nil
142✔
3047
}
3048

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3204
        rpcConfig := miningNode.RPCConfig()
4✔
3205

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

3221
        for _, walletDriver := range lnwallet.RegisteredWallets() {
8✔
3222
                for _, backEnd := range walletDriver.BackEnds() {
20✔
3223
                        if backEnd != targetBackEnd {
28✔
3224
                                continue
12✔
3225
                        }
3226

3227
                        if !runTests(t, walletDriver, backEnd, miningNode,
4✔
3228
                                rpcConfig, chainNotifier) {
4✔
3229

×
3230
                                return
×
3231
                        }
×
3232
                }
3233
        }
3234
}
3235

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

4✔
3245
        var (
4✔
3246
                bio lnwallet.BlockChainIO
4✔
3247

4✔
3248
                aliceSigner input.Signer
4✔
3249
                bobSigner   input.Signer
4✔
3250

4✔
3251
                aliceKeyRing keychain.SecretKeyRing
4✔
3252
                bobKeyRing   keychain.SecretKeyRing
4✔
3253

4✔
3254
                aliceWalletController lnwallet.WalletController
4✔
3255
                bobWalletController   lnwallet.WalletController
4✔
3256

4✔
3257
                err error
4✔
3258
        )
4✔
3259

4✔
3260
        tempTestDirAlice := t.TempDir()
4✔
3261
        tempTestDirBob := t.TempDir()
4✔
3262

4✔
3263
        blockCache := blockcache.NewBlockCache(10000)
4✔
3264

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

3288
                case "neutrino":
1✔
3289
                        // Set some package-level variable to speed up
1✔
3290
                        // operation for tests.
1✔
3291
                        neutrino.BanDuration = time.Millisecond * 100
1✔
3292
                        neutrino.QueryTimeout = time.Millisecond * 500
1✔
3293
                        neutrino.QueryNumRetries = 1
1✔
3294

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

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

3355
                case "bitcoind":
1✔
3356
                        // Start a bitcoind instance.
1✔
3357
                        chainConn := unittest.NewBitcoindBackend(
1✔
3358
                                t, unittest.NetParams, miningNode, true, false,
1✔
3359
                        )
1✔
3360

1✔
3361
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3362
                        // Bob.
1✔
3363
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3364
                        bobClient = chainConn.NewBitcoindClient()
1✔
3365

3366
                case "bitcoind-rpc-polling":
1✔
3367
                        // Start a bitcoind instance.
1✔
3368
                        chainConn := unittest.NewBitcoindBackend(
1✔
3369
                                t, unittest.NetParams, miningNode, true, true,
1✔
3370
                        )
1✔
3371

1✔
3372
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3373
                        // Bob.
1✔
3374
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3375
                        bobClient = chainConn.NewBitcoindClient()
1✔
3376

3377
                default:
×
3378
                        t.Fatalf("unknown chain driver: %v", backEnd)
×
3379
                }
3380

3381
                aliceSeed := sha256.New()
4✔
3382
                aliceSeed.Write([]byte(backEnd))
4✔
3383
                aliceSeed.Write(aliceHDSeed[:])
4✔
3384
                aliceSeedBytes := aliceSeed.Sum(nil)
4✔
3385

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

4✔
3412
                bobSeed := sha256.New()
4✔
3413
                bobSeed.Write([]byte(backEnd))
4✔
3414
                bobSeed.Write(bobHDSeed[:])
4✔
3415
                bobSeedBytes := bobSeed.Sum(nil)
4✔
3416

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

3447
        // Funding via 20 outputs with 4BTC each.
3448
        alice := createTestWallet(
4✔
3449
                t, tempTestDirAlice, miningNode, netParams,
4✔
3450
                chainNotifier, aliceWalletController, aliceKeyRing,
4✔
3451
                aliceSigner, bio,
4✔
3452
        )
4✔
3453

4✔
3454
        bob := createTestWallet(
4✔
3455
                t, tempTestDirBob, miningNode, netParams,
4✔
3456
                chainNotifier, bobWalletController, bobKeyRing, bobSigner, bio,
4✔
3457
        )
4✔
3458

4✔
3459
        // Both wallets should now have 80BTC available for
4✔
3460
        // spending.
4✔
3461
        assertProperBalance(t, alice, 1, 80)
4✔
3462
        assertProperBalance(t, bob, 1, 80)
4✔
3463

4✔
3464
        // Execute every test, clearing possibly mutated
4✔
3465
        // wallet state after each step.
4✔
3466
        for _, walletTest := range walletTests {
92✔
3467

88✔
3468
                walletTest := walletTest
88✔
3469

88✔
3470
                testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
88✔
3471
                        walletTest.name)
88✔
3472
                success := t.Run(testName, func(t *testing.T) {
176✔
3473
                        if backEnd == "neutrino" &&
88✔
3474
                                strings.Contains(walletTest.name, "dual funder") {
89✔
3475

1✔
3476
                                t.Skip("skipping dual funder tests for neutrino")
1✔
3477
                        }
1✔
3478
                        if backEnd == "neutrino" &&
87✔
3479
                                strings.Contains(walletTest.name, "spend unconfirmed") {
88✔
3480

1✔
3481
                                t.Skip("skipping spend unconfirmed tests for neutrino")
1✔
3482
                        }
1✔
3483

3484
                        walletTest.test(miningNode, alice, bob, t)
86✔
3485
                })
3486
                if !success {
88✔
3487
                        return false
×
3488
                }
×
3489

3490
                // TODO(roasbeef): possible reset mining
3491
                // node's chainstate to initial level, cleanly
3492
                // wipe buckets
3493
                if err := clearWalletStates(alice, bob); err !=
88✔
3494
                        nil && err != kvdb.ErrBucketNotFound {
88✔
3495

×
3496
                        t.Fatalf("unable to wipe wallet state: %v", err)
×
3497
                }
×
3498
        }
3499

3500
        return true
4✔
3501
}
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