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

lightningnetwork / lnd / 13558005087

27 Feb 2025 03:04AM UTC coverage: 58.834% (-0.001%) from 58.835%
13558005087

Pull #8453

github

Roasbeef
lnwallet/chancloser: increase test coverage of state machine
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

1079 of 1370 new or added lines in 23 files covered. (78.76%)

578 existing lines in 40 files now uncovered.

137063 of 232965 relevant lines covered (58.83%)

19205.84 hits per line

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

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

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

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

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

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

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

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

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

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

90
        csvDelay uint16 = 4
91

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

95
        defaultMaxLocalCsvDelay uint16 = 10000
96
)
97

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

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

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

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

34✔
123
        t.Helper()
34✔
124

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

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

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

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

18✔
154
        t.Helper()
18✔
155

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

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

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

40✔
170
        t.Helper()
40✔
171

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

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

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

189
        return tx
40✔
190
}
191

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

64✔
197
        t.Helper()
64✔
198

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

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

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

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

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

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

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

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

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

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

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

20✔
309
        return nil
20✔
310
}
311

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

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

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

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

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

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

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

8✔
347
        return wallet
8✔
348
}
349

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1602
        return keyScript, nil
66✔
1603
}
1604

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1730
        return tx1, nil
27✔
1731
}
1732

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

12✔
1738
        t.Helper()
12✔
1739

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

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

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

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

1770
        return tx1
12✔
1771
}
1772

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4✔
3256
                err error
4✔
3257
        )
4✔
3258

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

88✔
3467
                walletTest := walletTest
88✔
3468

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

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

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

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

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

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

3499
        return true
3500
}
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