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

lightningnetwork / lnd / 13522615507

25 Feb 2025 01:40PM UTC coverage: 58.836% (+0.02%) from 58.815%
13522615507

Pull #9550

github

ellemouton
graph/db: move various cache write calls to ChannelGraph

Here, we move the graph cache writes for AddLightningNode,
DeleteLightningNode, AddChannelEdge and MarkEdgeLive to the
ChannelGraph. Since these are writes, the cache is only updated if the
DB write is successful.
Pull Request #9550: graph: extract cache from CRUD [3]

73 of 85 new or added lines in 1 file covered. (85.88%)

275 existing lines in 12 files now uncovered.

136412 of 231851 relevant lines covered (58.84%)

19316.27 hits per line

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

78.69
/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,040✔
207
                if tx.Hash != txHash {
3,888✔
208
                        continue
1,912✔
209
                }
210
                if tx.NumConfirmations <= 0 && confirmed {
64✔
211
                        t.Fatalf("expected transaction %v to be confirmed",
×
212
                                txHash)
×
213
                }
×
214
                if tx.NumConfirmations > 0 && !confirmed {
64✔
215
                        t.Fatalf("expected transaction %v to be unconfirmed",
×
216
                                txHash)
×
217
                }
×
218

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

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

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

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

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

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

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

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

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

20✔
287
        for range ticker.C {
90✔
288
                balance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
70✔
289
                if err != nil {
70✔
290
                        return err
×
291
                }
×
292
                if balance == expectedBalance {
90✔
293
                        break
20✔
294
                }
295
                select {
50✔
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:
50✔
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 {
1,916✔
2977
                // Do a short wait
1,759✔
2978
                select {
1,759✔
2979
                case <-timeout:
×
2980
                        return fmt.Errorf("timeout after 10s")
×
2981
                default:
1,759✔
2982
                }
2983
                time.Sleep(100 * time.Millisecond)
1,759✔
2984

1,759✔
2985
                // Check for the harness' knowledge of the txid
1,759✔
2986
                tx, err = r.Client.GetRawTransaction(txid)
1,759✔
2987
                if err != nil {
3,361✔
2988
                        switch e := err.(type) {
1,602✔
2989
                        case *btcjson.RPCError:
1,602✔
2990
                                if e.Code == btcjson.ErrRPCNoTxInfo {
3,204✔
2991
                                        continue
1,602✔
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 {
334✔
3013
                // Do a short wait
192✔
3014
                select {
192✔
3015
                case <-timeout:
×
3016
                        return fmt.Errorf("timeout after 30s")
×
3017
                case <-time.Tick(100 * time.Millisecond):
192✔
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()
192✔
3023
                if err != nil {
192✔
3024
                        return err
×
3025
                }
×
3026
                knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
192✔
3027
                if err != nil {
192✔
3028
                        return err
×
3029
                }
×
3030
                if knownHeight != bestHeight {
225✔
3031
                        continue
33✔
3032
                }
3033
                if *knownHash != *bestHash {
159✔
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()
159✔
3041
                if err != nil {
159✔
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()
1✔
3363

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()
1✔
3374
                        bobClient = chainConn.NewBitcoindClient()
1✔
3375

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

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✔
3401
                )
4✔
3402
                if err != nil {
4✔
3403
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3404
                }
×
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✔
3432
                )
4✔
3433
                if err != nil {
4✔
3434
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3435
                }
×
3436
                bobSigner = bobWalletController.(*btcwallet.BtcWallet)
4✔
3437
                bobKeyRing = keychain.NewBtcWalletKeyRing(
4✔
3438
                        bobWalletController.(*btcwallet.BtcWallet).InternalWallet(),
4✔
3439
                        keychain.CoinTypeTestnet,
4✔
3440
                )
4✔
3441
                bio = bobWalletController.(*btcwallet.BtcWallet)
4✔
3442
        default:
×
3443
                t.Fatalf("unknown wallet driver: %v", walletType)
×
3444
        }
3445

3446
        // Funding via 20 outputs with 4BTC each.
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
4✔
3464
        // wallet state after each step.
4✔
3465
        for _, walletTest := range walletTests {
92✔
3466

88✔
3467
                walletTest := walletTest
88✔
3468

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

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

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

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

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

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

3499
        return true
4✔
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