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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

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

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

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

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

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

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

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

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

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

90
        csvDelay uint16 = 4
91

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

95
        defaultMaxLocalCsvDelay uint16 = 10000
96
)
97

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

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

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

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

34✔
123
        t.Helper()
34✔
124

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

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

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

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

18✔
154
        t.Helper()
18✔
155

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

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

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

40✔
170
        t.Helper()
40✔
171

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

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

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

189
        return tx
40✔
190
}
191

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

64✔
197
        t.Helper()
64✔
198

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

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

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

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

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

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

261
                addrs = append(addrs, walletAddr)
400✔
262

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

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

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

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

20✔
307
        return nil
20✔
308
}
309

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

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

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
        if err != nil {
8✔
337
                return nil, err
×
338
        }
×
339

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

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

349
        return wallet, nil
8✔
350
}
351

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4✔
1263
        // Fetch the transaction details again, the new transaction should be
4✔
1264
        // shown as debiting from the wallet's balance. Start and end height
4✔
1265
        // are inclusive, so we use chainTip for both parameters to get only
4✔
1266
        // transactions from the last block.
4✔
1267
        err = waitForWalletSync(miner, alice)
4✔
1268
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1269
        txDetails, err = alice.ListTransactionDetails(chainTip, chainTip, "")
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(chainTip, chainTip, "")
4✔
1311
        require.NoError(t, err, "unexpected error")
4✔
1312
        if len(txDetails) != 0 {
4✔
1313
                t.Fatalf("expected 0 transactions, got: %v", len(txDetails))
×
1314
        }
×
1315
}
1316

1317
func testTransactionSubscriptions(miner *rpctest.Harness,
1318
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1319

4✔
1320
        // First, check to see if this wallet meets the TransactionNotifier
4✔
1321
        // interface, if not then we'll skip this test for this particular
4✔
1322
        // implementation of the WalletController.
4✔
1323
        txClient, err := alice.SubscribeTransactions()
4✔
1324
        if err != nil {
4✔
1325
                t.Skipf("unable to generate tx subscription: %v", err)
×
1326
        }
×
1327
        defer txClient.Cancel()
4✔
1328

4✔
1329
        const (
4✔
1330
                outputAmt = btcutil.SatoshiPerBitcoin
4✔
1331
                numTxns   = 3
4✔
1332
        )
4✔
1333
        errCh1 := make(chan error, 1)
4✔
1334
        switch alice.BackEnd() {
4✔
1335
        case "neutrino":
1✔
1336
                // Neutrino doesn't listen for unconfirmed transactions.
1337
        default:
3✔
1338
                go func() {
6✔
1339
                        for i := 0; i < numTxns; i++ {
12✔
1340
                                txDetail := <-txClient.UnconfirmedTransactions()
9✔
1341
                                if txDetail.NumConfirmations != 0 {
9✔
1342
                                        errCh1 <- fmt.Errorf("incorrect number of confs, "+
×
1343
                                                "expected %v got %v", 0,
×
1344
                                                txDetail.NumConfirmations)
×
1345
                                        return
×
1346
                                }
×
1347
                                if txDetail.Value != outputAmt {
9✔
1348
                                        errCh1 <- fmt.Errorf("incorrect output amt, "+
×
1349
                                                "expected %v got %v", outputAmt,
×
1350
                                                txDetail.Value)
×
1351
                                        return
×
1352
                                }
×
1353
                                if txDetail.BlockHash != nil {
9✔
1354
                                        errCh1 <- fmt.Errorf("block hash should be nil, "+
×
1355
                                                "is instead %v",
×
1356
                                                txDetail.BlockHash)
×
1357
                                        return
×
1358
                                }
×
1359
                        }
1360
                        errCh1 <- nil
3✔
1361
                }()
1362
        }
1363

1364
        // Next, fetch a fresh address from the wallet, create 3 new outputs
1365
        // with the pkScript.
1366
        for i := 0; i < numTxns; i++ {
16✔
1367
                addr, err := alice.NewAddress(
12✔
1368
                        lnwallet.WitnessPubKey, false,
12✔
1369
                        lnwallet.DefaultAccountName,
12✔
1370
                )
12✔
1371
                if err != nil {
12✔
1372
                        t.Fatalf("unable to create new address: %v", err)
×
1373
                }
×
1374
                script, err := txscript.PayToAddrScript(addr)
12✔
1375
                if err != nil {
12✔
1376
                        t.Fatalf("unable to create output script: %v", err)
×
1377
                }
×
1378

1379
                output := &wire.TxOut{
12✔
1380
                        Value:    outputAmt,
12✔
1381
                        PkScript: script,
12✔
1382
                }
12✔
1383
                txid, err := miner.SendOutputs([]*wire.TxOut{output}, 2500)
12✔
1384
                if err != nil {
12✔
1385
                        t.Fatalf("unable to send coinbase: %v", err)
×
1386
                }
×
1387
                err = waitForMempoolTx(miner, txid)
12✔
1388
                if err != nil {
12✔
1389
                        t.Fatalf("tx not relayed to miner: %v", err)
×
1390
                }
×
1391
        }
1392

1393
        switch alice.BackEnd() {
4✔
1394
        case "neutrino":
1✔
1395
                // Neutrino doesn't listen for on unconfirmed transactions.
1396
        default:
3✔
1397
                // We should receive a notification for all three transactions
3✔
1398
                // generated above.
3✔
1399
                select {
3✔
1400
                case <-time.After(time.Second * 10):
×
1401
                        t.Fatalf("transactions not received after 10 seconds")
×
1402
                case err := <-errCh1:
3✔
1403
                        if err != nil {
3✔
1404
                                t.Fatal(err)
×
1405
                        }
×
1406
                }
1407
        }
1408

1409
        errCh2 := make(chan error, 1)
4✔
1410
        go func() {
8✔
1411
                for i := 0; i < numTxns; i++ {
16✔
1412
                        txDetail := <-txClient.ConfirmedTransactions()
12✔
1413
                        if txDetail.NumConfirmations != 1 {
12✔
1414
                                errCh2 <- fmt.Errorf("incorrect number of confs for %s, expected %v got %v",
×
1415
                                        txDetail.Hash, 1, txDetail.NumConfirmations)
×
1416
                                return
×
1417
                        }
×
1418
                        if txDetail.Value != outputAmt {
12✔
1419
                                errCh2 <- fmt.Errorf("incorrect output amt, expected %v got %v in txid %s",
×
1420
                                        outputAmt, txDetail.Value, txDetail.Hash)
×
1421
                                return
×
1422
                        }
×
1423
                }
1424
                errCh2 <- nil
4✔
1425
        }()
1426

1427
        // Next mine a single block, all the transactions generated above
1428
        // should be included.
1429
        if _, err := miner.Client.Generate(1); err != nil {
4✔
1430
                t.Fatalf("unable to generate block: %v", err)
×
1431
        }
×
1432

1433
        // We should receive a notification for all three transactions
1434
        // since they should be mined in the next block.
1435
        select {
4✔
1436
        case <-time.After(time.Second * 5):
×
1437
                t.Fatalf("transactions not received after 5 seconds")
×
1438
        case err := <-errCh2:
4✔
1439
                if err != nil {
4✔
1440
                        t.Fatal(err)
×
1441
                }
×
1442
        }
1443

1444
        // We'll also ensure that the client is able to send our new
1445
        // notifications when we _create_ transactions ourselves that spend our
1446
        // own outputs.
1447
        addr, err := alice.NewAddress(
4✔
1448
                lnwallet.WitnessPubKey, false,
4✔
1449
                lnwallet.DefaultAccountName,
4✔
1450
        )
4✔
1451
        require.NoError(t, err)
4✔
1452

4✔
1453
        outputScript, err := txscript.PayToAddrScript(addr)
4✔
1454
        require.NoError(t, err)
4✔
1455

4✔
1456
        burnOutput := wire.NewTxOut(outputAmt, outputScript)
4✔
1457
        tx, err := alice.SendOutputs(
4✔
1458
                nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
4✔
1459
                alice.Cfg.CoinSelectionStrategy,
4✔
1460
        )
4✔
1461
        require.NoError(t, err, "unable to create tx")
4✔
1462
        txid := tx.TxHash()
4✔
1463
        err = waitForMempoolTx(miner, &txid)
4✔
1464
        require.NoError(t, err, "tx not relayed to miner")
4✔
1465

4✔
1466
        // Before we mine the next block, we'll ensure that the above
4✔
1467
        // transaction shows up in the set of unconfirmed transactions returned
4✔
1468
        // by ListTransactionDetails.
4✔
1469
        err = waitForWalletSync(miner, alice)
4✔
1470
        require.NoError(t, err, "Couldn't sync Alice's wallet")
4✔
1471

4✔
1472
        // As we just sent the transaction and it was landed in the mempool, we
4✔
1473
        // should get a notification for a new unconfirmed transactions
4✔
1474
        select {
4✔
1475
        case <-time.After(time.Second * 10):
×
1476
                t.Fatalf("transactions not received after 10 seconds")
×
1477
        case unConfTx := <-txClient.UnconfirmedTransactions():
4✔
1478
                if unConfTx.Hash != txid {
4✔
1479
                        t.Fatalf("wrong txn notified: expected %v got %v",
×
1480
                                txid, unConfTx.Hash)
×
1481
                }
×
1482
        }
1483
}
1484

1485
// scriptFromKey creates a P2WKH script from the given pubkey.
1486
func scriptFromKey(pubkey *btcec.PublicKey) ([]byte, error) {
102✔
1487
        pubkeyHash := btcutil.Hash160(pubkey.SerializeCompressed())
102✔
1488
        keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(
102✔
1489
                pubkeyHash, &chaincfg.RegressionNetParams,
102✔
1490
        )
102✔
1491
        if err != nil {
102✔
1492
                return nil, fmt.Errorf("unable to create addr: %w", err)
×
1493
        }
×
1494
        keyScript, err := txscript.PayToAddrScript(keyAddr)
102✔
1495
        if err != nil {
102✔
1496
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1497
        }
×
1498

1499
        return keyScript, nil
102✔
1500
}
1501

1502
// mineAndAssert mines a block and ensures the passed TX is part of that block.
1503
func mineAndAssert(r *rpctest.Harness, tx *wire.MsgTx) error {
36✔
1504
        txid := tx.TxHash()
36✔
1505
        err := waitForMempoolTx(r, &txid)
36✔
1506
        if err != nil {
36✔
1507
                return fmt.Errorf("tx not relayed to miner: %w", err)
×
1508
        }
×
1509

1510
        blockHashes, err := r.Client.Generate(1)
36✔
1511
        if err != nil {
36✔
1512
                return fmt.Errorf("unable to generate block: %w", err)
×
1513
        }
×
1514

1515
        block, err := r.Client.GetBlock(blockHashes[0])
36✔
1516
        if err != nil {
36✔
1517
                return fmt.Errorf("unable to find block: %w", err)
×
1518
        }
×
1519

1520
        if len(block.Transactions) != 2 {
36✔
1521
                return fmt.Errorf("expected 2 txs in block, got %d",
×
1522
                        len(block.Transactions))
×
1523
        }
×
1524

1525
        blockTx := block.Transactions[1]
36✔
1526
        if blockTx.TxHash() != tx.TxHash() {
36✔
1527
                return fmt.Errorf("incorrect transaction was mined")
×
1528
        }
×
1529

1530
        // Sleep for a second before returning, to make sure the block has
1531
        // propagated.
1532
        time.Sleep(1 * time.Second)
36✔
1533
        return nil
36✔
1534
}
1535

1536
// txFromOutput takes a tx paying to fromPubKey, and creates a new tx that
1537
// spends the output from this tx, to an address derived from payToPubKey.
1538
func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey,
1539
        payToPubKey *btcec.PublicKey, txFee btcutil.Amount,
1540
        rbf bool) (*wire.MsgTx, error) {
43✔
1541

43✔
1542
        // Generate the script we want to spend from.
43✔
1543
        keyScript, err := scriptFromKey(fromPubKey)
43✔
1544
        if err != nil {
43✔
1545
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1546
        }
×
1547

1548
        // We assume the output was paid to the keyScript made earlier.
1549
        var outputIndex uint32
43✔
1550
        if len(tx.TxOut) == 1 || bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
81✔
1551
                outputIndex = 0
38✔
1552
        } else {
43✔
1553
                outputIndex = 1
5✔
1554
        }
5✔
1555
        outputValue := tx.TxOut[outputIndex].Value
43✔
1556

43✔
1557
        // With the index located, we can create a transaction spending the
43✔
1558
        // referenced output.
43✔
1559
        tx1 := wire.NewMsgTx(2)
43✔
1560

43✔
1561
        // If we want to create a tx that signals replacement, set its
43✔
1562
        // sequence number to the max one that signals replacement.
43✔
1563
        // Otherwise we just use the standard max sequence.
43✔
1564
        sequence := wire.MaxTxInSequenceNum
43✔
1565
        if rbf {
55✔
1566
                sequence = mempool.MaxRBFSequence
12✔
1567
        }
12✔
1568

1569
        tx1.AddTxIn(&wire.TxIn{
43✔
1570
                PreviousOutPoint: wire.OutPoint{
43✔
1571
                        Hash:  tx.TxHash(),
43✔
1572
                        Index: outputIndex,
43✔
1573
                },
43✔
1574
                Sequence: sequence,
43✔
1575
        })
43✔
1576

43✔
1577
        // Create a script to pay to.
43✔
1578
        payToScript, err := scriptFromKey(payToPubKey)
43✔
1579
        if err != nil {
43✔
1580
                return nil, fmt.Errorf("unable to generate script: %w", err)
×
1581
        }
×
1582
        tx1.AddTxOut(&wire.TxOut{
43✔
1583
                Value:    outputValue - int64(txFee),
43✔
1584
                PkScript: payToScript,
43✔
1585
        })
43✔
1586

43✔
1587
        // Now we can populate the sign descriptor which we'll use to generate
43✔
1588
        // the signature.
43✔
1589
        signDesc := &input.SignDescriptor{
43✔
1590
                KeyDesc: keychain.KeyDescriptor{
43✔
1591
                        PubKey: fromPubKey,
43✔
1592
                },
43✔
1593
                WitnessScript: keyScript,
43✔
1594
                Output:        tx.TxOut[outputIndex],
43✔
1595
                HashType:      txscript.SigHashAll,
43✔
1596
                SigHashes:     input.NewTxSigHashesV0Only(tx1),
43✔
1597
                InputIndex:    0, // Has only one input.
43✔
1598
        }
43✔
1599

43✔
1600
        // With the descriptor created, we use it to generate a signature, then
43✔
1601
        // manually create a valid witness stack we'll use for signing.
43✔
1602
        spendSig, err := signer.SignOutputRaw(tx1, signDesc)
43✔
1603
        if err != nil {
43✔
1604
                return nil, fmt.Errorf("unable to generate signature: %w", err)
×
1605
        }
×
1606
        witness := make([][]byte, 2)
43✔
1607
        witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
43✔
1608
        witness[1] = fromPubKey.SerializeCompressed()
43✔
1609
        tx1.TxIn[0].Witness = witness
43✔
1610

43✔
1611
        // Finally, attempt to validate the completed transaction. This should
43✔
1612
        // succeed if the wallet was able to properly generate the proper
43✔
1613
        // private key.
43✔
1614
        vm, err := txscript.NewEngine(
43✔
1615
                keyScript, tx1, 0, txscript.StandardVerifyFlags, nil,
43✔
1616
                nil, outputValue, txscript.NewCannedPrevOutputFetcher(
43✔
1617
                        keyScript, outputValue,
43✔
1618
                ),
43✔
1619
        )
43✔
1620
        if err != nil {
43✔
1621
                return nil, fmt.Errorf("unable to create engine: %w", err)
×
1622
        }
×
1623
        if err := vm.Execute(); err != nil {
43✔
1624
                return nil, fmt.Errorf("spend is invalid: %w", err)
×
1625
        }
×
1626

1627
        return tx1, nil
43✔
1628
}
1629

1630
// newTx sends coins from Alice's wallet, mines this transaction, and creates a
1631
// new, unconfirmed tx that spends this output to pubKey.
1632
func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
1633
        alice *lnwallet.LightningWallet, rbf bool) *wire.MsgTx {
16✔
1634

16✔
1635
        t.Helper()
16✔
1636

16✔
1637
        keyScript, err := scriptFromKey(pubKey)
16✔
1638
        require.NoError(t, err, "unable to generate script")
16✔
1639

16✔
1640
        // Instruct the wallet to fund the output with a newly created
16✔
1641
        // transaction.
16✔
1642
        newOutput := &wire.TxOut{
16✔
1643
                Value:    btcutil.SatoshiPerBitcoin,
16✔
1644
                PkScript: keyScript,
16✔
1645
        }
16✔
1646
        tx, err := alice.SendOutputs(
16✔
1647
                nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
16✔
1648
                alice.Cfg.CoinSelectionStrategy,
16✔
1649
        )
16✔
1650
        require.NoError(t, err, "unable to create output")
16✔
1651

16✔
1652
        // Query for the transaction generated above so we can located the
16✔
1653
        // index of our output.
16✔
1654
        if err := mineAndAssert(r, tx); err != nil {
16✔
1655
                t.Fatalf("unable to mine tx: %v", err)
×
1656
        }
×
1657

1658
        // Create a new unconfirmed tx that spends this output.
1659
        txFee := btcutil.Amount(0.001 * btcutil.SatoshiPerBitcoin)
16✔
1660
        tx1, err := txFromOutput(
16✔
1661
                tx, alice.Cfg.Signer, pubKey, pubKey, txFee, rbf,
16✔
1662
        )
16✔
1663
        if err != nil {
16✔
1664
                t.Fatal(err)
×
1665
        }
×
1666

1667
        return tx1
16✔
1668
}
1669

1670
// testGetTransactionDetails checks that GetTransactionDetails returns the
1671
// correct amount after a transaction has been sent from alice to bob.
1672
func testGetTransactionDetails(r *rpctest.Harness,
1673
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
1674

4✔
1675
        const txFee = int64(14500)
4✔
1676

4✔
1677
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
4✔
1678
        txFeeRate := chainfee.SatPerKWeight(2500)
4✔
1679
        amountSats := btcutil.Amount(btcutil.SatoshiPerBitcoin - txFee)
4✔
1680
        output := &wire.TxOut{
4✔
1681
                Value:    int64(amountSats),
4✔
1682
                PkScript: bobPkScript,
4✔
1683
        }
4✔
1684
        tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
1685
        txHash := tx.TxHash()
4✔
1686
        assertTxInWallet(t, alice, txHash, true)
4✔
1687
        assertTxInWallet(t, bob, txHash, true)
4✔
1688

4✔
1689
        txDetails, err := bob.GetTransactionDetails(&txHash)
4✔
1690
        require.NoError(t, err, "unable to receive transaction details")
4✔
1691
        require.Equal(t, txDetails.Value, amountSats, "tx value")
4✔
1692
}
4✔
1693

1694
// testPublishTransaction checks that PublishTransaction returns the expected
1695
// error types in case the transaction being published conflicts with the
1696
// current mempool or chain.
1697
func testPublishTransaction(r *rpctest.Harness,
1698
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1699

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

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

4✔
1709
                // Publish the transaction.
4✔
1710
                err = alice.PublishTransaction(tx1, labels.External)
4✔
1711
                require.NoError(t, err)
4✔
1712

4✔
1713
                txid1 := tx1.TxHash()
4✔
1714
                err = waitForMempoolTx(r, &txid1)
4✔
1715
                require.NoError(t, err, "tx not relayed to miner")
4✔
1716

4✔
1717
                // Publish the exact same transaction again. This should not
4✔
1718
                // return an error, even though the transaction is already in
4✔
1719
                // the mempool.
4✔
1720
                err = alice.PublishTransaction(tx1, labels.External)
4✔
1721
                require.NoError(t, err)
4✔
1722

4✔
1723
                // Mine the transaction.
4✔
1724
                _, err := r.Client.Generate(1)
4✔
1725
                require.NoError(t, err)
4✔
1726
        })
4✔
1727

1728
        t.Run("no_error_on_minged_tx", func(t *testing.T) {
8✔
1729
                // We'll now test that we don't get an error if we try to
4✔
1730
                // publish a transaction that is already mined.
4✔
1731
                //
4✔
1732
                // Create a new transaction. We must do this to properly test
4✔
1733
                // the reject messages from our peers. They might only send us
4✔
1734
                // a reject message for a given tx once, so we create a new to
4✔
1735
                // make sure it is not just immediately rejected.
4✔
1736
                tx2 := newTx(t, r, keyDesc.PubKey, alice, false)
4✔
1737

4✔
1738
                // Publish this tx.
4✔
1739
                err = alice.PublishTransaction(tx2, labels.External)
4✔
1740
                require.NoError(t, err)
4✔
1741

4✔
1742
                // Mine the transaction.
4✔
1743
                err := mineAndAssert(r, tx2)
4✔
1744
                require.NoError(t, err)
4✔
1745

4✔
1746
                // Publish the transaction again. It is already mined, and we
4✔
1747
                // don't expect this to return an error.
4✔
1748
                err = alice.PublishTransaction(tx2, labels.External)
4✔
1749
                require.NoError(t, err)
4✔
1750
        })
4✔
1751

1752
        // We'll do the next mempool check on both RBF and non-RBF
1753
        // enabled transactions.
1754
        var (
4✔
1755
                txFee = btcutil.Amount(
4✔
1756
                        0.005 * btcutil.SatoshiPerBitcoin,
4✔
1757
                )
4✔
1758
                tx3, tx3Spend *wire.MsgTx
4✔
1759
        )
4✔
1760
        t.Run("rbf_tests", func(t *testing.T) {
8✔
1761
                for _, rbf := range []bool{false, true} {
12✔
1762
                        // Now we'll try to double spend an output with a
8✔
1763
                        // different transaction. Create a new tx and publish
8✔
1764
                        // it. This is the output we'll try to double spend.
8✔
1765
                        tx3 = newTx(t, r, keyDesc.PubKey, alice, false)
8✔
1766
                        err := alice.PublishTransaction(tx3, labels.External)
8✔
1767
                        require.NoError(t, err)
8✔
1768

8✔
1769
                        // Mine the transaction.
8✔
1770
                        err = mineAndAssert(r, tx3)
8✔
1771
                        require.NoError(t, err)
8✔
1772

8✔
1773
                        // Now we create a transaction that spends the output
8✔
1774
                        // from the tx just mined.
8✔
1775
                        tx4, err := txFromOutput(
8✔
1776
                                tx3, alice.Cfg.Signer, keyDesc.PubKey,
8✔
1777
                                keyDesc.PubKey, txFee, rbf,
8✔
1778
                        )
8✔
1779
                        require.NoError(t, err)
8✔
1780

8✔
1781
                        // This should be accepted into the mempool.
8✔
1782
                        err = alice.PublishTransaction(tx4, labels.External)
8✔
1783
                        require.NoError(t, err)
8✔
1784

8✔
1785
                        // Keep track of the last successfully published tx to
8✔
1786
                        // spend tx3.
8✔
1787
                        tx3Spend = tx4
8✔
1788

8✔
1789
                        txid4 := tx4.TxHash()
8✔
1790
                        err = waitForMempoolTx(r, &txid4)
8✔
1791
                        require.NoError(t, err, "tx not relayed to miner")
8✔
1792

8✔
1793
                        // Create a new key we'll pay to, to ensure we create a
8✔
1794
                        // unique transaction.
8✔
1795
                        keyDesc2, err := alice.DeriveNextKey(
8✔
1796
                                keychain.KeyFamilyMultiSig,
8✔
1797
                        )
8✔
1798
                        require.NoError(t, err, "unable to obtain public key")
8✔
1799

8✔
1800
                        // Create a new transaction that spends the output from
8✔
1801
                        // tx3, and that pays to a different address.
8✔
1802
                        tx5, err := txFromOutput(
8✔
1803
                                tx3, alice.Cfg.Signer, keyDesc.PubKey,
8✔
1804
                                keyDesc2.PubKey, txFee, rbf,
8✔
1805
                        )
8✔
1806
                        require.NoError(t, err)
8✔
1807

8✔
1808
                        err = alice.PublishTransaction(tx5, labels.External)
8✔
1809

8✔
1810
                        // If RBF is not enabled, we expect this to be rejected
8✔
1811
                        // because it is a double spend.
8✔
1812
                        expectedErr := lnwallet.ErrDoubleSpend
8✔
1813

8✔
1814
                        // If RBF is enabled, we expect it to be rejected
8✔
1815
                        // because it doesn't pay enough fees.
8✔
1816
                        if rbf {
12✔
1817
                                expectedErr = chain.ErrInsufficientFee
4✔
1818
                        }
4✔
1819

1820
                        // Assert the expected error.
1821
                        require.ErrorIsf(t, err, expectedErr, "has rbf=%v", rbf)
8✔
1822

8✔
1823
                        // Create another transaction that spends the same
8✔
1824
                        // output, but has a higher fee. We expect also this tx
8✔
1825
                        // to be rejected for non-RBF enabled transactions,
8✔
1826
                        // while it should succeed otherwise.
8✔
1827
                        pubKey3, err := alice.DeriveNextKey(
8✔
1828
                                keychain.KeyFamilyMultiSig,
8✔
1829
                        )
8✔
1830
                        require.NoError(t, err, "unable to obtain public key")
8✔
1831

8✔
1832
                        tx6, err := txFromOutput(
8✔
1833
                                tx3, alice.Cfg.Signer, keyDesc.PubKey,
8✔
1834
                                pubKey3.PubKey, 2*txFee, rbf,
8✔
1835
                        )
8✔
1836
                        require.NoError(t, err)
8✔
1837

8✔
1838
                        // Expect rejection in non-RBF case.
8✔
1839
                        expErr := lnwallet.ErrDoubleSpend
8✔
1840
                        if rbf {
12✔
1841
                                // Expect success in rbf case.
4✔
1842
                                expErr = nil
4✔
1843
                                tx3Spend = tx6
4✔
1844
                        }
4✔
1845
                        err = alice.PublishTransaction(tx6, labels.External)
8✔
1846
                        require.ErrorIs(t, err, expErr)
8✔
1847

8✔
1848
                        // Mine the tx spending tx3.
8✔
1849
                        err = mineAndAssert(r, tx3Spend)
8✔
1850
                        require.NoError(t, err)
8✔
1851
                }
1852
        })
1853

1854
        t.Run("tx_double_spend", func(t *testing.T) {
8✔
1855
                // At last we try to spend an output already spent by a
4✔
1856
                // confirmed transaction.
4✔
1857
                //
4✔
1858
                // TODO(halseth): we currently skip this test for neutrino, as
4✔
1859
                // the backing btcd node will consider the tx being an orphan,
4✔
1860
                // and will accept it. Should look into if this is the behavior
4✔
1861
                // also for bitcoind, and update test accordingly.
4✔
1862
                if alice.BackEnd() != "neutrino" {
7✔
1863
                        // Create another tx spending tx3.
3✔
1864
                        pubKey4, err := alice.DeriveNextKey(
3✔
1865
                                keychain.KeyFamilyMultiSig,
3✔
1866
                        )
3✔
1867
                        require.NoError(t, err, "unable to obtain public key")
3✔
1868

3✔
1869
                        tx7, err := txFromOutput(
3✔
1870
                                tx3, alice.Cfg.Signer, keyDesc.PubKey,
3✔
1871
                                pubKey4.PubKey, txFee, false,
3✔
1872
                        )
3✔
1873
                        require.NoError(t, err)
3✔
1874

3✔
1875
                        // Expect rejection.
3✔
1876
                        err = alice.PublishTransaction(tx7, labels.External)
3✔
1877
                        require.ErrorIs(t, err, lnwallet.ErrDoubleSpend)
3✔
1878
                }
3✔
1879
        })
1880

1881
        t.Run("test_tx_size_limit", func(t *testing.T) {
8✔
1882
                // In this test, we'll try to create a massive transaction that
4✔
1883
                // can't be mined as dictacted by widely deployed transaction
4✔
1884
                // policy.
4✔
1885
                //
4✔
1886
                // To do this, we'll take out of the prior transactions, and
4✔
1887
                // add a bunch of outputs, putting it over the max weight
4✔
1888
                // limit.
4✔
1889
                testTx := tx3.Copy()
4✔
1890
                for i := 0; i < blockchain.MaxOutputsPerBlock; i++ {
444,448✔
1891
                        testTx.AddTxOut(&wire.TxOut{
444,444✔
1892
                                Value:    tx3.TxOut[0].Value,
444,444✔
1893
                                PkScript: tx3.TxOut[0].PkScript,
444,444✔
1894
                        })
444,444✔
1895
                }
444,444✔
1896

1897
                // Now broadcast the transaction, we should get an error that
1898
                // the weight is too large.
1899
                err := alice.PublishTransaction(testTx, labels.External)
4✔
1900
                require.ErrorIs(t, err, chain.ErrOversizeTx)
4✔
1901
        })
1902
}
1903

1904
func testSignOutputUsingTweaks(r *rpctest.Harness,
1905
        alice, _ *lnwallet.LightningWallet, t *testing.T) {
4✔
1906

4✔
1907
        // We'd like to test the ability of the wallet's Signer implementation
4✔
1908
        // to be able to sign with a private key derived from tweaking the
4✔
1909
        // specific public key. This scenario exercises the case when the
4✔
1910
        // wallet needs to sign for a sweep of a revoked output, or just claim
4✔
1911
        // any output that pays to a tweaked key.
4✔
1912

4✔
1913
        // First, generate a new public key under the control of the wallet,
4✔
1914
        // then generate a revocation key using it.
4✔
1915
        pubKey, err := alice.DeriveNextKey(
4✔
1916
                keychain.KeyFamilyMultiSig,
4✔
1917
        )
4✔
1918
        require.NoError(t, err, "unable to obtain public key")
4✔
1919

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

4✔
1926
        revocationKey := input.DeriveRevocationPubkey(pubKey.PubKey, commitPoint)
4✔
1927
        commitTweak := input.SingleTweakBytes(commitPoint, pubKey.PubKey)
4✔
1928

4✔
1929
        tweakedPub := input.TweakPubKey(pubKey.PubKey, commitPoint)
4✔
1930

4✔
1931
        // As we'd like to test both single and double tweaks, we'll repeat
4✔
1932
        // the same set up twice. The first will use a regular single tweak,
4✔
1933
        // and the second will use a double tweak.
4✔
1934
        baseKey := pubKey
4✔
1935
        for i := 0; i < 2; i++ {
12✔
1936
                var tweakedKey *btcec.PublicKey
8✔
1937
                if i == 0 {
12✔
1938
                        tweakedKey = tweakedPub
4✔
1939
                } else {
8✔
1940
                        tweakedKey = revocationKey
4✔
1941
                }
4✔
1942

1943
                // Using the given key for the current iteration, we'll
1944
                // generate a regular p2wkh from that.
1945
                pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
8✔
1946
                keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
8✔
1947
                        &chaincfg.RegressionNetParams)
8✔
1948
                if err != nil {
8✔
1949
                        t.Fatalf("unable to create addr: %v", err)
×
1950
                }
×
1951
                keyScript, err := txscript.PayToAddrScript(keyAddr)
8✔
1952
                if err != nil {
8✔
1953
                        t.Fatalf("unable to generate script: %v", err)
×
1954
                }
×
1955

1956
                // With the script fully assembled, instruct the wallet to fund
1957
                // the output with a newly created transaction.
1958
                newOutput := &wire.TxOut{
8✔
1959
                        Value:    btcutil.SatoshiPerBitcoin,
8✔
1960
                        PkScript: keyScript,
8✔
1961
                }
8✔
1962
                tx, err := alice.SendOutputs(
8✔
1963
                        nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
8✔
1964
                        alice.Cfg.CoinSelectionStrategy,
8✔
1965
                )
8✔
1966
                if err != nil {
8✔
1967
                        t.Fatalf("unable to create output: %v", err)
×
1968
                }
×
1969
                txid := tx.TxHash()
8✔
1970
                // Query for the transaction generated above so we can located
8✔
1971
                // the index of our output.
8✔
1972
                err = waitForMempoolTx(r, &txid)
8✔
1973
                if err != nil {
8✔
1974
                        t.Fatalf("tx not relayed to miner: %v", err)
×
1975
                }
×
1976
                var outputIndex uint32
8✔
1977
                if bytes.Equal(tx.TxOut[0].PkScript, keyScript) {
11✔
1978
                        outputIndex = 0
3✔
1979
                } else {
8✔
1980
                        outputIndex = 1
5✔
1981
                }
5✔
1982

1983
                // With the index located, we can create a transaction spending
1984
                // the referenced output.
1985
                sweepTx := wire.NewMsgTx(2)
8✔
1986
                sweepTx.AddTxIn(&wire.TxIn{
8✔
1987
                        PreviousOutPoint: wire.OutPoint{
8✔
1988
                                Hash:  txid,
8✔
1989
                                Index: outputIndex,
8✔
1990
                        },
8✔
1991
                })
8✔
1992
                sweepTx.AddTxOut(&wire.TxOut{
8✔
1993
                        Value:    1000,
8✔
1994
                        PkScript: keyScript,
8✔
1995
                })
8✔
1996

8✔
1997
                // Now we can populate the sign descriptor which we'll use to
8✔
1998
                // generate the signature. Within the descriptor we set the
8✔
1999
                // private tweak value as the key in the script is derived
8✔
2000
                // based on this tweak value and the key we originally
8✔
2001
                // generated above.
8✔
2002
                signDesc := &input.SignDescriptor{
8✔
2003
                        KeyDesc: keychain.KeyDescriptor{
8✔
2004
                                PubKey: baseKey.PubKey,
8✔
2005
                        },
8✔
2006
                        WitnessScript: keyScript,
8✔
2007
                        Output:        newOutput,
8✔
2008
                        HashType:      txscript.SigHashAll,
8✔
2009
                        SigHashes:     input.NewTxSigHashesV0Only(sweepTx),
8✔
2010
                        InputIndex:    0,
8✔
2011
                }
8✔
2012

8✔
2013
                // If this is the first, loop, we'll use the generated single
8✔
2014
                // tweak, otherwise, we'll use the double tweak.
8✔
2015
                if i == 0 {
12✔
2016
                        signDesc.SingleTweak = commitTweak
4✔
2017
                } else {
8✔
2018
                        signDesc.DoubleTweak = commitSecret
4✔
2019
                }
4✔
2020

2021
                // With the descriptor created, we use it to generate a
2022
                // signature, then manually create a valid witness stack we'll
2023
                // use for signing.
2024
                spendSig, err := alice.Cfg.Signer.SignOutputRaw(sweepTx, signDesc)
8✔
2025
                if err != nil {
8✔
2026
                        t.Fatalf("unable to generate signature: %v", err)
×
2027
                }
×
2028
                witness := make([][]byte, 2)
8✔
2029
                witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll))
8✔
2030
                witness[1] = tweakedKey.SerializeCompressed()
8✔
2031
                sweepTx.TxIn[0].Witness = witness
8✔
2032

8✔
2033
                // Finally, attempt to validate the completed transaction. This
8✔
2034
                // should succeed if the wallet was able to properly generate
8✔
2035
                // the proper private key.
8✔
2036
                vm, err := txscript.NewEngine(
8✔
2037
                        keyScript, sweepTx, 0, txscript.StandardVerifyFlags,
8✔
2038
                        nil, nil, int64(btcutil.SatoshiPerBitcoin),
8✔
2039
                        txscript.NewCannedPrevOutputFetcher(
8✔
2040
                                keyScript, int64(btcutil.SatoshiPerBitcoin),
8✔
2041
                        ),
8✔
2042
                )
8✔
2043
                if err != nil {
8✔
2044
                        t.Fatalf("unable to create engine: %v", err)
×
2045
                }
×
2046
                if err := vm.Execute(); err != nil {
8✔
2047
                        t.Fatalf("spend #%v is invalid: %v", i, err)
×
2048
                }
×
2049
        }
2050
}
2051

2052
func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
2053
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2054

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

4✔
2063
        // Give wallet time to catch up.
4✔
2064
        err = waitForWalletSync(r, w)
4✔
2065
        require.NoError(t, err, "unable to sync wallet")
4✔
2066

4✔
2067
        // Send some money from the miner to the wallet
4✔
2068
        err = loadTestCredits(r, w, 20, 4)
4✔
2069
        require.NoError(t, err, "unable to send money to lnwallet")
4✔
2070

4✔
2071
        // Send some money from the wallet back to the miner.
4✔
2072
        // Grab a fresh address from the miner to house this output.
4✔
2073
        minerAddr, err := r.NewAddress()
4✔
2074
        require.NoError(t, err, "unable to generate address for miner")
4✔
2075
        script, err := txscript.PayToAddrScript(minerAddr)
4✔
2076
        require.NoError(t, err, "unable to create pay to addr script")
4✔
2077
        output := &wire.TxOut{
4✔
2078
                Value:    1e8,
4✔
2079
                PkScript: script,
4✔
2080
        }
4✔
2081
        tx, err := w.SendOutputs(
4✔
2082
                nil, []*wire.TxOut{output}, 2500, 1, labels.External,
4✔
2083
                w.Cfg.CoinSelectionStrategy,
4✔
2084
        )
4✔
2085
        require.NoError(t, err, "unable to send outputs")
4✔
2086
        txid := tx.TxHash()
4✔
2087
        err = waitForMempoolTx(r, &txid)
4✔
2088
        require.NoError(t, err, "tx not relayed to miner")
4✔
2089
        _, err = r.Client.Generate(50)
4✔
2090
        require.NoError(t, err, "unable to generate blocks on passed node")
4✔
2091

4✔
2092
        // Give wallet time to catch up.
4✔
2093
        err = waitForWalletSync(r, w)
4✔
2094
        require.NoError(t, err, "unable to sync wallet")
4✔
2095

4✔
2096
        // Get the original balance.
4✔
2097
        origBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2098
        require.NoError(t, err, "unable to query for balance")
4✔
2099

4✔
2100
        // Now we cause a reorganization as follows.
4✔
2101
        // Step 1: create a new miner and start it.
4✔
2102
        r2 := unittest.NewMiner(
4✔
2103
                t, r.ActiveNet, []string{"--txindex"}, false, 0,
4✔
2104
        )
4✔
2105
        newBalance, err := w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2106
        require.NoError(t, err, "unable to query for balance")
4✔
2107
        if origBalance != newBalance {
4✔
2108
                t.Fatalf("wallet balance incorrect, should have %v, "+
×
2109
                        "instead have %v", origBalance, newBalance)
×
2110
        }
×
2111

2112
        // Step 2: connect the miner to the passed miner and wait for
2113
        // synchronization.
2114
        err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
4✔
2115
        require.NoError(t, err, "unable to connect mining nodes together")
4✔
2116
        err = rpctest.JoinNodes([]*rpctest.Harness{r2, r}, rpctest.Blocks)
4✔
2117
        require.NoError(t, err, "unable to synchronize mining nodes")
4✔
2118

4✔
2119
        // Step 3: Do a set of reorgs by disconnecting the two miners, mining
4✔
2120
        // one block on the passed miner and two on the created miner,
4✔
2121
        // connecting them, and waiting for them to sync.
4✔
2122
        for i := 0; i < 5; i++ {
24✔
2123
                // Wait for disconnection
20✔
2124
                timeout := time.After(30 * time.Second)
20✔
2125
                stillConnected := true
20✔
2126
                var peers []btcjson.GetPeerInfoResult
20✔
2127
                for stillConnected {
41✔
2128
                        // Allow for timeout
21✔
2129
                        time.Sleep(100 * time.Millisecond)
21✔
2130
                        select {
21✔
2131
                        case <-timeout:
×
2132
                                t.Fatalf("timeout waiting for miner disconnect")
×
2133
                        default:
21✔
2134
                        }
2135
                        err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANRemove)
21✔
2136
                        if err != nil {
21✔
2137
                                t.Fatalf("unable to disconnect mining nodes: %v",
×
2138
                                        err)
×
2139
                        }
×
2140
                        peers, err = r2.Client.GetPeerInfo()
21✔
2141
                        if err != nil {
21✔
2142
                                t.Fatalf("unable to get peer info: %v", err)
×
2143
                        }
×
2144
                        stillConnected = false
21✔
2145
                        for _, peer := range peers {
22✔
2146
                                if peer.Addr == r.P2PAddress() {
2✔
2147
                                        stillConnected = true
1✔
2148
                                        break
1✔
2149
                                }
2150
                        }
2151
                }
2152
                _, err = r.Client.Generate(2)
20✔
2153
                if err != nil {
20✔
2154
                        t.Fatalf("unable to generate blocks on passed node: %v",
×
2155
                                err)
×
2156
                }
×
2157
                _, err = r2.Client.Generate(3)
20✔
2158
                if err != nil {
20✔
2159
                        t.Fatalf("unable to generate blocks on created node: %v",
×
2160
                                err)
×
2161
                }
×
2162

2163
                // Step 5: Reconnect the miners and wait for them to synchronize.
2164
                err = r2.Client.AddNode(r.P2PAddress(), rpcclient.ANAdd)
20✔
2165
                if err != nil {
20✔
2166
                        switch err := err.(type) {
×
2167
                        case *btcjson.RPCError:
×
2168
                                if err.Code != -8 {
×
2169
                                        t.Fatalf("unable to connect mining "+
×
2170
                                                "nodes together: %v", err)
×
2171
                                }
×
2172
                        default:
×
2173
                                t.Fatalf("unable to connect mining nodes "+
×
2174
                                        "together: %v", err)
×
2175
                        }
2176
                }
2177
                err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
20✔
2178
                        rpctest.Blocks)
20✔
2179
                if err != nil {
20✔
2180
                        t.Fatalf("unable to synchronize mining nodes: %v", err)
×
2181
                }
×
2182

2183
                // Give wallet time to catch up.
2184
                err = waitForWalletSync(r, w)
20✔
2185
                if err != nil {
20✔
2186
                        t.Fatalf("unable to sync wallet: %v", err)
×
2187
                }
×
2188
        }
2189

2190
        // Now we check that the wallet balance stays the same.
2191
        newBalance, err = w.ConfirmedBalance(1, lnwallet.DefaultAccountName)
4✔
2192
        require.NoError(t, err, "unable to query for balance")
4✔
2193
        if origBalance != newBalance {
4✔
2194
                t.Fatalf("wallet balance incorrect, should have %v, "+
×
2195
                        "instead have %v", origBalance, newBalance)
×
2196
        }
×
2197
}
2198

2199
// testChangeOutputSpendConfirmation ensures that when we attempt to spend a
2200
// change output created by the wallet, the wallet receives its confirmation
2201
// once included in the chain.
2202
func testChangeOutputSpendConfirmation(r *rpctest.Harness,
2203
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2204

4✔
2205
        // In order to test that we see the confirmation of a transaction that
4✔
2206
        // spends an output created by SendOutputs, we'll start by emptying
4✔
2207
        // Alice's wallet so that no other UTXOs can be picked. To do so, we'll
4✔
2208
        // generate an address for Bob, who will receive all the coins.
4✔
2209
        // Assuming a balance of 80 BTC and a transaction fee of 2500 sat/kw,
4✔
2210
        // we'll craft the following transaction so that Alice doesn't have any
4✔
2211
        // UTXOs left.
4✔
2212
        aliceBalance, err := alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
4✔
2213
        require.NoError(t, err, "unable to retrieve alice's balance")
4✔
2214
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
4✔
2215

4✔
2216
        // We'll use a transaction fee of 14380 satoshis, which will allow us to
4✔
2217
        // sweep all of Alice's balance in one transaction containing 1 input
4✔
2218
        // and 1 output.
4✔
2219
        //
4✔
2220
        // TODO(wilmer): replace this once SendOutputs easily supports sending
4✔
2221
        // all funds in one transaction.
4✔
2222
        txFeeRate := chainfee.SatPerKWeight(2500)
4✔
2223
        const txFee = int64(14500)
4✔
2224
        output := &wire.TxOut{
4✔
2225
                Value:    int64(aliceBalance) - txFee,
4✔
2226
                PkScript: bobPkScript,
4✔
2227
        }
4✔
2228
        tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2229
        txHash := tx.TxHash()
4✔
2230
        assertTxInWallet(t, alice, txHash, true)
4✔
2231
        assertTxInWallet(t, bob, txHash, true)
4✔
2232

4✔
2233
        // With the transaction sent and confirmed, Alice's balance should now
4✔
2234
        // be 0.
4✔
2235
        aliceBalance, err = alice.ConfirmedBalance(0, lnwallet.DefaultAccountName)
4✔
2236
        require.NoError(t, err, "unable to retrieve alice's balance")
4✔
2237
        if aliceBalance != 0 {
4✔
2238
                t.Fatalf("expected alice's balance to be 0 BTC, found %v",
×
2239
                        aliceBalance)
×
2240
        }
×
2241

2242
        // Now, we'll send an output back to Alice from Bob of 1 BTC.
2243
        alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
4✔
2244
        output = &wire.TxOut{
4✔
2245
                Value:    btcutil.SatoshiPerBitcoin,
4✔
2246
                PkScript: alicePkScript,
4✔
2247
        }
4✔
2248
        tx = sendCoins(t, r, bob, alice, output, txFeeRate, true, 1)
4✔
2249
        txHash = tx.TxHash()
4✔
2250
        assertTxInWallet(t, alice, txHash, true)
4✔
2251
        assertTxInWallet(t, bob, txHash, true)
4✔
2252

4✔
2253
        // Alice now has an available output to spend, but it was not a change
4✔
2254
        // output, which is what the test expects. Therefore, we'll generate one
4✔
2255
        // by sending Bob back some coins.
4✔
2256
        output = &wire.TxOut{
4✔
2257
                Value:    btcutil.SatoshiPerBitcent,
4✔
2258
                PkScript: bobPkScript,
4✔
2259
        }
4✔
2260
        tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2261
        txHash = tx.TxHash()
4✔
2262
        assertTxInWallet(t, alice, txHash, true)
4✔
2263
        assertTxInWallet(t, bob, txHash, true)
4✔
2264

4✔
2265
        // Then, we'll spend the change output and ensure we see its
4✔
2266
        // confirmation come in.
4✔
2267
        tx = sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
4✔
2268
        txHash = tx.TxHash()
4✔
2269
        assertTxInWallet(t, alice, txHash, true)
4✔
2270
        assertTxInWallet(t, bob, txHash, true)
4✔
2271

4✔
2272
        // Finally, we'll replenish Alice's wallet with some more coins to
4✔
2273
        // ensure she has enough for any following test cases.
4✔
2274
        if err := loadTestCredits(r, alice, 20, 4); err != nil {
4✔
2275
                t.Fatalf("unable to replenish alice's wallet: %v", err)
×
2276
        }
×
2277
}
2278

2279
// testSpendUnconfirmed ensures that when can spend unconfirmed outputs.
2280
func testSpendUnconfirmed(miner *rpctest.Harness,
2281
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
3✔
2282

3✔
2283
        bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
3✔
2284
        alicePkScript := newPkScript(t, alice, lnwallet.WitnessPubKey)
3✔
2285
        txFeeRate := chainfee.SatPerKWeight(2500)
3✔
2286

3✔
2287
        // First we will empty out bob's wallet, sending the entire balance
3✔
2288
        // to alice.
3✔
2289
        bobBalance, err := bob.ConfirmedBalance(0, lnwallet.DefaultAccountName)
3✔
2290
        require.NoError(t, err, "unable to retrieve bob's balance")
3✔
2291
        txFee := btcutil.Amount(28760)
3✔
2292
        output := &wire.TxOut{
3✔
2293
                Value:    int64(bobBalance - txFee),
3✔
2294
                PkScript: alicePkScript,
3✔
2295
        }
3✔
2296
        tx := sendCoins(t, miner, bob, alice, output, txFeeRate, true, 1)
3✔
2297
        txHash := tx.TxHash()
3✔
2298
        assertTxInWallet(t, alice, txHash, true)
3✔
2299
        assertTxInWallet(t, bob, txHash, true)
3✔
2300

3✔
2301
        // Verify that bob doesn't have enough balance to send coins.
3✔
2302
        output = &wire.TxOut{
3✔
2303
                Value:    btcutil.SatoshiPerBitcoin * 0.5,
3✔
2304
                PkScript: alicePkScript,
3✔
2305
        }
3✔
2306
        _, err = bob.SendOutputs(
3✔
2307
                nil, []*wire.TxOut{output}, txFeeRate, 0, labels.External,
3✔
2308
                bob.Cfg.CoinSelectionStrategy,
3✔
2309
        )
3✔
2310
        if err == nil {
3✔
2311
                t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
×
2312
        }
×
2313

2314
        // Next we will send a transaction to bob but leave it in an
2315
        // unconfirmed state.
2316
        output = &wire.TxOut{
3✔
2317
                Value:    btcutil.SatoshiPerBitcoin,
3✔
2318
                PkScript: bobPkScript,
3✔
2319
        }
3✔
2320
        tx = sendCoins(t, miner, alice, bob, output, txFeeRate, false, 1)
3✔
2321
        txHash = tx.TxHash()
3✔
2322
        assertTxInWallet(t, alice, txHash, false)
3✔
2323
        assertTxInWallet(t, bob, txHash, false)
3✔
2324

3✔
2325
        // Now, try to spend some of the unconfirmed funds from bob's wallet.
3✔
2326
        output = &wire.TxOut{
3✔
2327
                Value:    btcutil.SatoshiPerBitcoin * 0.5,
3✔
2328
                PkScript: alicePkScript,
3✔
2329
        }
3✔
2330

3✔
2331
        // First, verify that we don't have enough balance to send the coins
3✔
2332
        // using confirmed outputs only.
3✔
2333
        _, err = bob.SendOutputs(
3✔
2334
                nil, []*wire.TxOut{output}, txFeeRate, 1, labels.External,
3✔
2335
                bob.Cfg.CoinSelectionStrategy,
3✔
2336
        )
3✔
2337
        if err == nil {
3✔
2338
                t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
×
2339
        }
×
2340

2341
        // Now try the send again using unconfirmed outputs.
2342
        tx = sendCoins(t, miner, bob, alice, output, txFeeRate, false, 0)
3✔
2343
        txHash = tx.TxHash()
3✔
2344
        assertTxInWallet(t, alice, txHash, false)
3✔
2345
        assertTxInWallet(t, bob, txHash, false)
3✔
2346

3✔
2347
        // Mine the unconfirmed transactions.
3✔
2348
        err = waitForMempoolTx(miner, &txHash)
3✔
2349
        require.NoError(t, err, "tx not relayed to miner")
3✔
2350
        if _, err := miner.Client.Generate(1); err != nil {
3✔
2351
                t.Fatalf("unable to generate block: %v", err)
×
2352
        }
×
2353
        if err := waitForWalletSync(miner, alice); err != nil {
3✔
2354
                t.Fatalf("unable to sync alice: %v", err)
×
2355
        }
×
2356
        if err := waitForWalletSync(miner, bob); err != nil {
3✔
2357
                t.Fatalf("unable to sync bob: %v", err)
×
2358
        }
×
2359

2360
        // Finally, send the remainder of bob's wallet balance back to him so
2361
        // that these money movements dont mess up later tests.
2362
        output = &wire.TxOut{
3✔
2363
                Value:    int64(bobBalance) - (btcutil.SatoshiPerBitcoin * 0.4),
3✔
2364
                PkScript: bobPkScript,
3✔
2365
        }
3✔
2366
        tx = sendCoins(t, miner, alice, bob, output, txFeeRate, true, 1)
3✔
2367
        txHash = tx.TxHash()
3✔
2368
        assertTxInWallet(t, alice, txHash, true)
3✔
2369
        assertTxInWallet(t, bob, txHash, true)
3✔
2370
}
2371

2372
// testLastUnusedAddr tests that the LastUnusedAddress returns the address if
2373
// it isn't used, and also that once the address becomes used, then it's
2374
// properly rotated.
2375
func testLastUnusedAddr(miner *rpctest.Harness,
2376
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2377

4✔
2378
        if _, err := miner.Client.Generate(1); err != nil {
4✔
2379
                t.Fatalf("unable to generate block: %v", err)
×
2380
        }
×
2381

2382
        // We'll repeat this test for each address type to ensure they're all
2383
        // rotated properly.
2384
        addrTypes := []lnwallet.AddressType{
4✔
2385
                lnwallet.WitnessPubKey, lnwallet.NestedWitnessPubKey,
4✔
2386
        }
4✔
2387
        for _, addrType := range addrTypes {
12✔
2388
                addr1, err := alice.LastUnusedAddress(
8✔
2389
                        addrType, lnwallet.DefaultAccountName,
8✔
2390
                )
8✔
2391
                if err != nil {
8✔
2392
                        t.Fatalf("unable to get addr: %v", err)
×
2393
                }
×
2394
                addr2, err := alice.LastUnusedAddress(
8✔
2395
                        addrType, lnwallet.DefaultAccountName,
8✔
2396
                )
8✔
2397
                if err != nil {
8✔
2398
                        t.Fatalf("unable to get addr: %v", err)
×
2399
                }
×
2400

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

2407
                // Next, we'll have Bob pay to Alice's new address. This should
2408
                // trigger address rotation at the backend wallet.
2409
                addrScript, err := txscript.PayToAddrScript(addr1)
8✔
2410
                if err != nil {
8✔
2411
                        t.Fatalf("unable to convert addr to script: %v", err)
×
2412
                }
×
2413
                feeRate := chainfee.SatPerKWeight(2500)
8✔
2414
                output := &wire.TxOut{
8✔
2415
                        Value:    1000000,
8✔
2416
                        PkScript: addrScript,
8✔
2417
                }
8✔
2418
                sendCoins(t, miner, bob, alice, output, feeRate, true, 1)
8✔
2419

8✔
2420
                // If we make a new address, then it should be brand new, as
8✔
2421
                // the prior address has been used.
8✔
2422
                addr3, err := alice.LastUnusedAddress(
8✔
2423
                        addrType, lnwallet.DefaultAccountName,
8✔
2424
                )
8✔
2425
                if err != nil {
8✔
2426
                        t.Fatalf("unable to get addr: %v", err)
×
2427
                }
×
2428
                if addr1.String() == addr3.String() {
8✔
2429
                        t.Fatalf("address should have changed but didn't")
×
2430
                }
×
2431
        }
2432
}
2433

2434
// testCreateSimpleTx checks that a call to CreateSimpleTx will return a
2435
// transaction that is equal to the one that is being created by SendOutputs in
2436
// a subsequent call.
2437
// All test cases are doubled-up: one for testing unconfirmed inputs,
2438
// one for testing only confirmed inputs.
2439
func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
2440
        _ *lnwallet.LightningWallet, t *testing.T) {
4✔
2441

4✔
2442
        // Send some money from the miner to the wallet
4✔
2443
        err := loadTestCredits(r, w, 20, 4)
4✔
2444
        require.NoError(t, err, "unable to send money to lnwallet")
4✔
2445

4✔
2446
        // The test cases we will run through for all backends.
4✔
2447
        testCases := []struct {
4✔
2448
                outVals     []int64
4✔
2449
                feeRate     chainfee.SatPerKWeight
4✔
2450
                valid       bool
4✔
2451
                unconfirmed bool
4✔
2452
        }{
4✔
2453
                {
4✔
2454
                        outVals:     []int64{},
4✔
2455
                        feeRate:     2500,
4✔
2456
                        valid:       false, // No outputs.
4✔
2457
                        unconfirmed: false,
4✔
2458
                },
4✔
2459
                {
4✔
2460
                        outVals:     []int64{},
4✔
2461
                        feeRate:     2500,
4✔
2462
                        valid:       false, // No outputs.
4✔
2463
                        unconfirmed: true,
4✔
2464
                },
4✔
2465

4✔
2466
                {
4✔
2467
                        outVals:     []int64{200},
4✔
2468
                        feeRate:     2500,
4✔
2469
                        valid:       false, // Dust output.
4✔
2470
                        unconfirmed: false,
4✔
2471
                },
4✔
2472
                {
4✔
2473
                        outVals:     []int64{200},
4✔
2474
                        feeRate:     2500,
4✔
2475
                        valid:       false, // Dust output.
4✔
2476
                        unconfirmed: true,
4✔
2477
                },
4✔
2478

4✔
2479
                {
4✔
2480
                        outVals:     []int64{1e8},
4✔
2481
                        feeRate:     2500,
4✔
2482
                        valid:       true,
4✔
2483
                        unconfirmed: false,
4✔
2484
                },
4✔
2485
                {
4✔
2486
                        outVals:     []int64{1e8},
4✔
2487
                        feeRate:     2500,
4✔
2488
                        valid:       true,
4✔
2489
                        unconfirmed: true,
4✔
2490
                },
4✔
2491

4✔
2492
                {
4✔
2493
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2494
                        feeRate:     2500,
4✔
2495
                        valid:       true,
4✔
2496
                        unconfirmed: false,
4✔
2497
                },
4✔
2498
                {
4✔
2499
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2500
                        feeRate:     2500,
4✔
2501
                        valid:       true,
4✔
2502
                        unconfirmed: true,
4✔
2503
                },
4✔
2504

4✔
2505
                {
4✔
2506
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2507
                        feeRate:     12500,
4✔
2508
                        valid:       true,
4✔
2509
                        unconfirmed: false,
4✔
2510
                },
4✔
2511
                {
4✔
2512
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2513
                        feeRate:     12500,
4✔
2514
                        valid:       true,
4✔
2515
                        unconfirmed: true,
4✔
2516
                },
4✔
2517

4✔
2518
                {
4✔
2519
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2520
                        feeRate:     50000,
4✔
2521
                        valid:       true,
4✔
2522
                        unconfirmed: false,
4✔
2523
                },
4✔
2524
                {
4✔
2525
                        outVals:     []int64{1e8, 2e8, 1e8, 2e7, 3e5},
4✔
2526
                        feeRate:     50000,
4✔
2527
                        valid:       true,
4✔
2528
                        unconfirmed: true,
4✔
2529
                },
4✔
2530

4✔
2531
                {
4✔
2532
                        outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
4✔
2533
                                1e8, 2e7, 3e5},
4✔
2534
                        feeRate:     44250,
4✔
2535
                        valid:       true,
4✔
2536
                        unconfirmed: false,
4✔
2537
                },
4✔
2538
                {
4✔
2539
                        outVals: []int64{1e8, 2e8, 1e8, 2e7, 3e5, 1e8, 2e8,
4✔
2540
                                1e8, 2e7, 3e5},
4✔
2541
                        feeRate:     44250,
4✔
2542
                        valid:       true,
4✔
2543
                        unconfirmed: true,
4✔
2544
                },
4✔
2545
        }
4✔
2546

4✔
2547
        for i, test := range testCases {
60✔
2548
                var minConfs int32 = 1
56✔
2549

56✔
2550
                feeRate := test.feeRate
56✔
2551

56✔
2552
                // Grab some fresh addresses from the miner that we will send
56✔
2553
                // to.
56✔
2554
                outputs := make([]*wire.TxOut, len(test.outVals))
56✔
2555
                for i, outVal := range test.outVals {
272✔
2556
                        minerAddr, err := r.NewAddress()
216✔
2557
                        if err != nil {
216✔
2558
                                t.Fatalf("unable to generate address for "+
×
2559
                                        "miner: %v", err)
×
2560
                        }
×
2561
                        script, err := txscript.PayToAddrScript(minerAddr)
216✔
2562
                        if err != nil {
216✔
2563
                                t.Fatalf("unable to create pay to addr "+
×
2564
                                        "script: %v", err)
×
2565
                        }
×
2566
                        output := &wire.TxOut{
216✔
2567
                                Value:    outVal,
216✔
2568
                                PkScript: script,
216✔
2569
                        }
216✔
2570

216✔
2571
                        outputs[i] = output
216✔
2572
                }
2573

2574
                // Now try creating a tx spending to these outputs.
2575
                createTx, createErr := w.CreateSimpleTx(
56✔
2576
                        nil, outputs, feeRate, minConfs,
56✔
2577
                        w.Cfg.CoinSelectionStrategy, true,
56✔
2578
                )
56✔
2579
                switch {
56✔
2580
                case test.valid && createErr != nil:
×
2581
                        fmt.Println(spew.Sdump(createTx.Tx))
×
2582
                        t.Fatalf("got unexpected error when creating tx: %v",
×
2583
                                createErr)
×
2584

2585
                case !test.valid && createErr == nil:
×
2586
                        t.Fatalf("test #%v should have failed on tx "+
×
2587
                                "creation", i)
×
2588
                }
2589

2590
                // Also send to these outputs. This should result in a tx
2591
                // _very_ similar to the one we just created being sent. The
2592
                // only difference is that the dry run tx is not signed, and
2593
                // that the change output position might be different.
2594
                tx, sendErr := w.SendOutputs(
56✔
2595
                        nil, outputs, feeRate, minConfs, labels.External,
56✔
2596
                        w.Cfg.CoinSelectionStrategy,
56✔
2597
                )
56✔
2598
                switch {
56✔
2599
                case test.valid && sendErr != nil:
×
2600
                        t.Fatalf("got unexpected error when sending tx: %v",
×
2601
                                sendErr)
×
2602

2603
                case !test.valid && sendErr == nil:
×
2604
                        t.Fatalf("test #%v should fail for tx sending", i)
×
2605
                }
2606

2607
                // We expected either both to not fail, or both to fail with
2608
                // the same error.
2609
                if createErr != sendErr {
56✔
2610
                        t.Fatalf("error creating tx (%v) different "+
×
2611
                                "from error sending outputs (%v)",
×
2612
                                createErr, sendErr)
×
2613
                }
×
2614

2615
                // If we expected the creation to fail, then this test is over.
2616
                if !test.valid {
72✔
2617
                        continue
16✔
2618
                }
2619

2620
                txid := tx.TxHash()
40✔
2621
                err = waitForMempoolTx(r, &txid)
40✔
2622
                if err != nil {
40✔
2623
                        t.Fatalf("tx not relayed to miner: %v", err)
×
2624
                }
×
2625

2626
                // Helper method to check that the two txs are similar.
2627
                assertSimilarTx := func(a, b *wire.MsgTx) error {
80✔
2628
                        if a.Version != b.Version {
40✔
2629
                                return fmt.Errorf("different versions: "+
×
2630
                                        "%v vs %v", a.Version, b.Version)
×
2631
                        }
×
2632
                        if a.LockTime != b.LockTime {
40✔
2633
                                return fmt.Errorf("different locktimes: "+
×
2634
                                        "%v vs %v", a.LockTime, b.LockTime)
×
2635
                        }
×
2636
                        if len(a.TxIn) != len(b.TxIn) {
40✔
2637
                                return fmt.Errorf("different number of "+
×
2638
                                        "inputs: %v vs %v", len(a.TxIn),
×
2639
                                        len(b.TxIn))
×
2640
                        }
×
2641
                        if len(a.TxOut) != len(b.TxOut) {
40✔
2642
                                return fmt.Errorf("different number of "+
×
2643
                                        "outputs: %v vs %v", len(a.TxOut),
×
2644
                                        len(b.TxOut))
×
2645
                        }
×
2646

2647
                        // They should be spending the same inputs.
2648
                        for i := range a.TxIn {
120✔
2649
                                prevA := a.TxIn[i].PreviousOutPoint
80✔
2650
                                prevB := b.TxIn[i].PreviousOutPoint
80✔
2651
                                if prevA != prevB {
80✔
2652
                                        return fmt.Errorf("different inputs: "+
×
2653
                                                "%v vs %v", spew.Sdump(prevA),
×
2654
                                                spew.Sdump(prevB))
×
2655
                                }
×
2656
                        }
2657

2658
                        // They should have the same outputs. Since the change
2659
                        // output position gets randomized, they are not
2660
                        // guaranteed to be in the same order.
2661
                        for _, outA := range a.TxOut {
288✔
2662
                                found := false
248✔
2663
                                for _, outB := range b.TxOut {
1,304✔
2664
                                        if reflect.DeepEqual(outA, outB) {
1,304✔
2665
                                                found = true
248✔
2666
                                                break
248✔
2667
                                        }
2668
                                }
2669
                                if !found {
248✔
2670
                                        return fmt.Errorf("did not find "+
×
2671
                                                "output %v", spew.Sdump(outA))
×
2672
                                }
×
2673
                        }
2674
                        return nil
40✔
2675
                }
2676

2677
                // Assert that our "template tx" was similar to the one that
2678
                // ended up being sent.
2679
                if err := assertSimilarTx(createTx.Tx, tx); err != nil {
40✔
2680
                        t.Fatalf("transactions not similar: %v", err)
×
2681
                }
×
2682

2683
                // Now that we know both transactions were essentially
2684
                // identical, we'll make sure that a P2TR addr was used as the
2685
                // change output, which is the current default.
2686
                changeTxOut := createTx.Tx.TxOut[createTx.ChangeIndex]
40✔
2687
                changeScriptType, _, _, err := txscript.ExtractPkScriptAddrs(
40✔
2688
                        changeTxOut.PkScript, &w.Cfg.NetParams,
40✔
2689
                )
40✔
2690
                require.NoError(t, err)
40✔
2691
                require.Equal(t, changeScriptType, txscript.WitnessV1TaprootTy)
40✔
2692
        }
2693
}
2694

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

4✔
2701
        // First, we'll create a sign desc that references a non-default key
4✔
2702
        // family. Under the hood, key families are actually accounts, so this
4✔
2703
        // should force create of the account so we can sign with it.
4✔
2704
        fakeTx := wire.NewMsgTx(2)
4✔
2705
        fakeTx.AddTxIn(&wire.TxIn{
4✔
2706
                PreviousOutPoint: wire.OutPoint{
4✔
2707
                        Hash:  chainhash.Hash{},
4✔
2708
                        Index: 0,
4✔
2709
                },
4✔
2710
        })
4✔
2711
        signDesc := &input.SignDescriptor{
4✔
2712
                KeyDesc: keychain.KeyDescriptor{
4✔
2713
                        KeyLocator: keychain.KeyLocator{
4✔
2714
                                Family: 99,
4✔
2715
                                Index:  1,
4✔
2716
                        },
4✔
2717
                },
4✔
2718
                WitnessScript: []byte{},
4✔
2719
                Output: &wire.TxOut{
4✔
2720
                        Value: 1000,
4✔
2721
                },
4✔
2722
                HashType:   txscript.SigHashAll,
4✔
2723
                SigHashes:  input.NewTxSigHashesV0Only(fakeTx),
4✔
2724
                InputIndex: 0,
4✔
2725
        }
4✔
2726

4✔
2727
        // We'll now sign and expect this to succeed, as even though the
4✔
2728
        // account doesn't exist atm, it should be created in order to process
4✔
2729
        // the inbound signing request.
4✔
2730
        _, err := w.Cfg.Signer.SignOutputRaw(fakeTx, signDesc)
4✔
2731
        if err != nil {
4✔
2732
                t.Fatalf("unable to sign for output with non-existent "+
×
2733
                        "account: %v", err)
×
2734
        }
×
2735
}
2736

2737
type walletTestCase struct {
2738
        name string
2739
        test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
2740
                test *testing.T)
2741
}
2742

2743
var walletTests = []walletTestCase{
2744
        {
2745
                // TODO(wilmer): this test should remain first until the wallet
2746
                // can properly craft a transaction that spends all of its
2747
                // on-chain funds.
2748
                name: "change output spend confirmation",
2749
                test: testChangeOutputSpendConfirmation,
2750
        },
2751
        {
2752
                // TODO(guggero): this test should remain second until dual
2753
                // funding can properly exchange full UTXO information and we
2754
                // can use P2TR change outputs as the funding inputs for a dual
2755
                // funded channel.
2756
                name: "dual funder workflow",
2757
                test: testDualFundingReservationWorkflow,
2758
        },
2759
        {
2760
                name: "spend unconfirmed outputs",
2761
                test: testSpendUnconfirmed,
2762
        },
2763
        {
2764
                name: "insane fee reject",
2765
                test: testReservationInitiatorBalanceBelowDustCancel,
2766
        },
2767
        {
2768
                name: "single funding workflow",
2769
                test: func(miner *rpctest.Harness, alice,
2770
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2771

4✔
2772
                        testSingleFunderReservationWorkflow(
4✔
2773
                                miner, alice, bob, t,
4✔
2774
                                lnwallet.CommitmentTypeLegacy, nil,
4✔
2775
                                nil, [32]byte{1}, 0,
4✔
2776
                        )
4✔
2777
                },
4✔
2778
        },
2779
        {
2780
                name: "single funding workflow tweakless",
2781
                test: func(miner *rpctest.Harness, alice,
2782
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2783

4✔
2784
                        testSingleFunderReservationWorkflow(
4✔
2785
                                miner, alice, bob, t,
4✔
2786
                                lnwallet.CommitmentTypeTweakless, nil,
4✔
2787
                                nil, [32]byte{1}, 0,
4✔
2788
                        )
4✔
2789
                },
4✔
2790
        },
2791
        {
2792
                name: "single funding workflow musig2",
2793
                test: func(miner *rpctest.Harness, alice,
2794
                        bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2795

4✔
2796
                        testSingleFunderReservationWorkflow(
4✔
2797
                                miner, alice, bob, t,
4✔
2798
                                lnwallet.CommitmentTypeSimpleTaproot, nil,
4✔
2799
                                nil, [32]byte{1}, 0,
4✔
2800
                        )
4✔
2801
                },
4✔
2802
        },
2803
        // TODO(roasbeef): add musig2 external funding
2804
        {
2805
                name: "single funding workflow external funding tx",
2806
                test: testSingleFunderExternalFundingTx,
2807
        },
2808
        {
2809
                name: "output locking",
2810
                test: testFundingTransactionLockedOutputs,
2811
        },
2812
        {
2813
                name: "reservation insufficient funds",
2814
                test: testFundingCancellationNotEnoughFunds,
2815
        },
2816
        {
2817
                name: "transaction subscriptions",
2818
                test: testTransactionSubscriptions,
2819
        },
2820
        {
2821
                name: "transaction details",
2822
                test: testListTransactionDetails,
2823
        },
2824
        {
2825
                name: "get transaction details",
2826
                test: testGetTransactionDetails,
2827
        },
2828
        {
2829
                name: "publish transaction",
2830
                test: testPublishTransaction,
2831
        },
2832
        {
2833
                name: "signed with tweaked pubkeys",
2834
                test: testSignOutputUsingTweaks,
2835
        },
2836
        {
2837
                name: "test cancel non-existent reservation",
2838
                test: testCancelNonExistentReservation,
2839
        },
2840
        {
2841
                name: "last unused addr",
2842
                test: testLastUnusedAddr,
2843
        },
2844
        {
2845
                name: "reorg wallet balance",
2846
                test: testReorgWalletBalance,
2847
        },
2848
        {
2849
                name: "create simple tx",
2850
                test: testCreateSimpleTx,
2851
        },
2852
        {
2853
                name: "test sign create account",
2854
                test: testSignOutputCreateAccount,
2855
        },
2856
        {
2857
                name: "test get recovery info",
2858
                test: testGetRecoveryInfo,
2859
        },
2860
}
2861

2862
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
84✔
2863
        a.ResetReservations()
84✔
2864
        b.ResetReservations()
84✔
2865

84✔
2866
        if err := a.Cfg.Database.GetParentDB().Wipe(); err != nil {
84✔
2867
                return err
×
2868
        }
×
2869

2870
        return b.Cfg.Database.GetParentDB().Wipe()
84✔
2871
}
2872

2873
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
173✔
2874
        var found bool
173✔
2875
        var tx *btcutil.Tx
173✔
2876
        var err error
173✔
2877
        timeout := time.After(30 * time.Second)
173✔
2878
        for !found {
1,941✔
2879
                // Do a short wait
1,768✔
2880
                select {
1,768✔
2881
                case <-timeout:
×
2882
                        return fmt.Errorf("timeout after 10s")
×
2883
                default:
1,768✔
2884
                }
2885
                time.Sleep(100 * time.Millisecond)
1,768✔
2886

1,768✔
2887
                // Check for the harness' knowledge of the txid
1,768✔
2888
                tx, err = r.Client.GetRawTransaction(txid)
1,768✔
2889
                if err != nil {
3,363✔
2890
                        switch e := err.(type) {
1,595✔
2891
                        case *btcjson.RPCError:
1,595✔
2892
                                if e.Code == btcjson.ErrRPCNoTxInfo {
3,190✔
2893
                                        continue
1,595✔
2894
                                }
2895
                        default:
×
2896
                        }
2897
                        return err
×
2898
                }
2899
                if tx != nil && tx.MsgTx().TxHash() == *txid {
346✔
2900
                        found = true
173✔
2901
                }
173✔
2902
        }
2903
        return nil
173✔
2904
}
2905

2906
func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
138✔
2907
        var (
138✔
2908
                synced                  bool
138✔
2909
                err                     error
138✔
2910
                bestHash, knownHash     *chainhash.Hash
138✔
2911
                bestHeight, knownHeight int32
138✔
2912
        )
138✔
2913
        timeout := time.After(10 * time.Second)
138✔
2914
        for !synced {
333✔
2915
                // Do a short wait
195✔
2916
                select {
195✔
2917
                case <-timeout:
×
2918
                        return fmt.Errorf("timeout after 30s")
×
2919
                case <-time.Tick(100 * time.Millisecond):
195✔
2920
                }
2921

2922
                // Check whether the chain source of the wallet is caught up to
2923
                // the harness it's supposed to be catching up to.
2924
                bestHash, bestHeight, err = r.Client.GetBestBlock()
195✔
2925
                if err != nil {
195✔
2926
                        return err
×
2927
                }
×
2928
                knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
195✔
2929
                if err != nil {
195✔
2930
                        return err
×
2931
                }
×
2932
                if knownHeight != bestHeight {
195✔
UNCOV
2933
                        continue
×
2934
                }
2935
                if *knownHash != *bestHash {
195✔
2936
                        return fmt.Errorf("hash at height %d doesn't match: "+
×
2937
                                "expected %s, got %s", bestHeight, bestHash,
×
2938
                                knownHash)
×
2939
                }
×
2940

2941
                // Check for synchronization.
2942
                synced, _, err = w.IsSynced()
195✔
2943
                if err != nil {
195✔
2944
                        return err
×
2945
                }
×
2946
        }
2947
        return nil
138✔
2948
}
2949

2950
// testSingleFunderExternalFundingTx tests that the wallet is able to properly
2951
// carry out a funding flow backed by a channel point that has been crafted
2952
// outside the wallet.
2953
func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
2954
        alice, bob *lnwallet.LightningWallet, t *testing.T) {
4✔
2955

4✔
2956
        // Define a filter function without any restrictions.
4✔
2957
        allowUtxo := func(lnwallet.Utxo) bool {
77✔
2958
                return true
73✔
2959
        }
73✔
2960

2961
        // First, we'll obtain multi-sig keys from both Alice and Bob which
2962
        // simulates them exchanging keys on a higher level.
2963
        aliceFundingKey, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
4✔
2964
        require.NoError(t, err, "unable to obtain alice funding key")
4✔
2965
        bobFundingKey, err := bob.DeriveNextKey(keychain.KeyFamilyMultiSig)
4✔
2966
        require.NoError(t, err, "unable to obtain bob funding key")
4✔
2967

4✔
2968
        // We'll now set up for them to open a 4 BTC channel, with 1 BTC pushed
4✔
2969
        // to Bob's side.
4✔
2970
        chanAmt := 4 * btcutil.SatoshiPerBitcoin
4✔
2971

4✔
2972
        // Simulating external funding negotiation, we'll now create the
4✔
2973
        // funding transaction for both parties. Utilizing existing tools,
4✔
2974
        // we'll create a new chanfunding.Assembler hacked by Alice's wallet.
4✔
2975
        aliceChanFunder := chanfunding.NewWalletAssembler(
4✔
2976
                chanfunding.WalletConfig{
4✔
2977
                        CoinSource: lnwallet.NewCoinSource(
4✔
2978
                                alice, allowUtxo,
4✔
2979
                        ),
4✔
2980
                        CoinSelectLocker:      alice,
4✔
2981
                        CoinLeaser:            alice,
4✔
2982
                        Signer:                alice.Cfg.Signer,
4✔
2983
                        DustLimit:             600,
4✔
2984
                        CoinSelectionStrategy: wallet.CoinSelectionLargest,
4✔
2985
                },
4✔
2986
        )
4✔
2987

4✔
2988
        // With the chan funder created, we'll now provision a funding intent,
4✔
2989
        // bind the keys we obtained above, and finally obtain our funding
4✔
2990
        // transaction and outpoint.
4✔
2991
        fundingIntent, err := aliceChanFunder.ProvisionChannel(
4✔
2992
                &chanfunding.Request{
4✔
2993
                        LocalAmt: btcutil.Amount(chanAmt),
4✔
2994
                        MinConfs: 1,
4✔
2995
                        FeeRate:  253,
4✔
2996
                        ChangeAddr: func() (btcutil.Address, error) {
8✔
2997
                                return alice.NewAddress(
4✔
2998
                                        lnwallet.WitnessPubKey, true,
4✔
2999
                                        lnwallet.DefaultAccountName,
4✔
3000
                                )
4✔
3001
                        },
4✔
3002
                },
3003
        )
3004
        require.NoError(t, err, "unable to perform coin selection")
4✔
3005

4✔
3006
        // With our intent created, we'll instruct it to finalize the funding
4✔
3007
        // transaction, and also hand us the outpoint so we can simulate
4✔
3008
        // external crafting of the funding transaction.
4✔
3009
        var (
4✔
3010
                fundingTx *wire.MsgTx
4✔
3011
                chanPoint *wire.OutPoint
4✔
3012
        )
4✔
3013
        if fullIntent, ok := fundingIntent.(*chanfunding.FullIntent); ok {
8✔
3014
                fullIntent.BindKeys(&aliceFundingKey, bobFundingKey.PubKey)
4✔
3015

4✔
3016
                fundingTx, err = fullIntent.CompileFundingTx(nil, nil)
4✔
3017
                if err != nil {
4✔
3018
                        t.Fatalf("unable to compile funding tx: %v", err)
×
3019
                }
×
3020
                chanPoint, err = fullIntent.ChanPoint()
4✔
3021
                if err != nil {
4✔
3022
                        t.Fatalf("unable to obtain chan point: %v", err)
×
3023
                }
×
3024
        } else {
×
3025
                t.Fatalf("expected full intent, instead got: %T", fullIntent)
×
3026
        }
×
3027

3028
        // Now that we have the fully constructed funding transaction, we'll
3029
        // create a new shim external funder out of it for Alice, and prep a
3030
        // shim intent for Bob.
3031
        thawHeight := uint32(200)
4✔
3032
        aliceExternalFunder := chanfunding.NewCannedAssembler(
4✔
3033
                thawHeight, *chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey,
4✔
3034
                bobFundingKey.PubKey, true, false,
4✔
3035
        )
4✔
3036
        bobShimIntent, err := chanfunding.NewCannedAssembler(
4✔
3037
                thawHeight, *chanPoint, btcutil.Amount(chanAmt), &bobFundingKey,
4✔
3038
                aliceFundingKey.PubKey, false, false,
4✔
3039
        ).ProvisionChannel(&chanfunding.Request{
4✔
3040
                LocalAmt: btcutil.Amount(chanAmt),
4✔
3041
                MinConfs: 1,
4✔
3042
                FeeRate:  253,
4✔
3043
                ChangeAddr: func() (btcutil.Address, error) {
4✔
3044
                        return bob.NewAddress(
×
3045
                                lnwallet.WitnessPubKey, true,
×
3046
                                lnwallet.DefaultAccountName,
×
3047
                        )
×
3048
                },
×
3049
        })
3050
        require.NoError(t, err, "unable to create shim intent for bob")
4✔
3051

4✔
3052
        // At this point, we have everything we need to carry out our test, so
4✔
3053
        // we'll being the funding flow between Alice and Bob.
4✔
3054
        //
4✔
3055
        // However, before we do so, we'll register a new shim intent for Bob,
4✔
3056
        // so he knows what keys to use when he receives the funding request
4✔
3057
        // from Alice.
4✔
3058
        pendingChanID := testHdSeed
4✔
3059
        err = bob.RegisterFundingIntent(pendingChanID, bobShimIntent)
4✔
3060
        require.NoError(t, err, "unable to register intent")
4✔
3061

4✔
3062
        // Now we can carry out the single funding flow as normal, we'll
4✔
3063
        // specify our external funder and funding transaction, as well as the
4✔
3064
        // pending channel ID generated above to allow Alice and Bob to track
4✔
3065
        // the funding flow externally.
4✔
3066
        testSingleFunderReservationWorkflow(
4✔
3067
                miner, alice, bob, t, lnwallet.CommitmentTypeTweakless,
4✔
3068
                aliceExternalFunder, func() *wire.MsgTx {
8✔
3069
                        return fundingTx
4✔
3070
                }, pendingChanID, thawHeight,
4✔
3071
        )
3072
}
3073

3074
// TestInterfaces tests all registered interfaces with a unified set of tests
3075
// which exercise each of the required methods found within the WalletController
3076
// interface.
3077
//
3078
// NOTE: In the future, when additional implementations of the WalletController
3079
// interface have been implemented, in order to ensure the new concrete
3080
// implementation is automatically tested, two steps must be undertaken. First,
3081
// one needs add a "non-captured" (_) import from the new sub-package. This
3082
// import should trigger an init() method within the package which registers
3083
// the interface. Second, an additional case in the switch within the main loop
3084
// below needs to be added which properly initializes the interface.
3085
//
3086
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
3087
func TestLightningWallet(t *testing.T, targetBackEnd string) {
4✔
3088
        t.Parallel()
4✔
3089

4✔
3090
        // Initialize the harness around a btcd node which will serve as our
4✔
3091
        // dedicated miner to generate blocks, cause re-orgs, etc. We'll set
4✔
3092
        // up this node with a chain length of 125, so we have plenty of BTC
4✔
3093
        // to play around with.
4✔
3094
        miningNode := unittest.NewMiner(
4✔
3095
                t, netParams, []string{"--txindex"}, true, 25,
4✔
3096
        )
4✔
3097

4✔
3098
        // Next mine enough blocks in order for segwit and the CSV package
4✔
3099
        // soft-fork to activate on RegNet.
4✔
3100
        numBlocks := netParams.MinerConfirmationWindow * 2
4✔
3101
        if _, err := miningNode.Client.Generate(numBlocks); err != nil {
4✔
3102
                t.Fatalf("unable to generate blocks: %v", err)
×
3103
        }
×
3104

3105
        rpcConfig := miningNode.RPCConfig()
4✔
3106

4✔
3107
        tempDir := t.TempDir()
4✔
3108
        db, err := channeldb.Open(tempDir)
4✔
3109
        require.NoError(t, err, "unable to create db")
4✔
3110
        testCfg := channeldb.CacheConfig{
4✔
3111
                QueryDisable: false,
4✔
3112
        }
4✔
3113
        hintCache, err := channeldb.NewHeightHintCache(testCfg, db.Backend)
4✔
3114
        require.NoError(t, err, "unable to create height hint cache")
4✔
3115
        blockCache := blockcache.NewBlockCache(10000)
4✔
3116
        chainNotifier, err := btcdnotify.New(
4✔
3117
                &rpcConfig, netParams, hintCache, hintCache, blockCache,
4✔
3118
        )
4✔
3119
        require.NoError(t, err, "unable to create notifier")
4✔
3120
        if err := chainNotifier.Start(); err != nil {
4✔
3121
                t.Fatalf("unable to start notifier: %v", err)
×
3122
        }
×
3123

3124
        for _, walletDriver := range lnwallet.RegisteredWallets() {
8✔
3125
                for _, backEnd := range walletDriver.BackEnds() {
20✔
3126
                        if backEnd != targetBackEnd {
28✔
3127
                                continue
12✔
3128
                        }
3129

3130
                        if !runTests(t, walletDriver, backEnd, miningNode,
4✔
3131
                                rpcConfig, chainNotifier) {
4✔
3132

×
3133
                                return
×
3134
                        }
×
3135
                }
3136
        }
3137
}
3138

3139
// runTests runs all of the tests for a single interface implementation and
3140
// chain back-end combination. This makes it easier to use `defer` as well as
3141
// factoring out the test logic from the loop which cycles through the
3142
// interface implementations.
3143
func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
3144
        backEnd string, miningNode *rpctest.Harness,
3145
        rpcConfig rpcclient.ConnConfig,
3146
        chainNotifier chainntnfs.ChainNotifier) bool {
4✔
3147

4✔
3148
        var (
4✔
3149
                bio lnwallet.BlockChainIO
4✔
3150

4✔
3151
                aliceSigner input.Signer
4✔
3152
                bobSigner   input.Signer
4✔
3153

4✔
3154
                aliceKeyRing keychain.SecretKeyRing
4✔
3155
                bobKeyRing   keychain.SecretKeyRing
4✔
3156

4✔
3157
                aliceWalletController lnwallet.WalletController
4✔
3158
                bobWalletController   lnwallet.WalletController
4✔
3159

4✔
3160
                err error
4✔
3161
        )
4✔
3162

4✔
3163
        tempTestDirAlice := t.TempDir()
4✔
3164
        tempTestDirBob := t.TempDir()
4✔
3165

4✔
3166
        blockCache := blockcache.NewBlockCache(10000)
4✔
3167

4✔
3168
        walletType := walletDriver.WalletType
4✔
3169
        switch walletType {
4✔
3170
        case "btcwallet":
4✔
3171
                var aliceClient, bobClient chain.Interface
4✔
3172
                switch backEnd {
4✔
3173
                case "btcd":
1✔
3174
                        aliceClient, err = chain.NewRPCClient(
1✔
3175
                                netParams, rpcConfig.Host, rpcConfig.User,
1✔
3176
                                rpcConfig.Pass, rpcConfig.Certificates, false,
1✔
3177
                                20,
1✔
3178
                        )
1✔
3179
                        if err != nil {
1✔
3180
                                t.Fatalf("unable to make chain rpc: %v", err)
×
3181
                        }
×
3182
                        bobClient, err = chain.NewRPCClient(
1✔
3183
                                netParams, rpcConfig.Host, rpcConfig.User,
1✔
3184
                                rpcConfig.Pass, rpcConfig.Certificates, false,
1✔
3185
                                20,
1✔
3186
                        )
1✔
3187
                        if err != nil {
1✔
3188
                                t.Fatalf("unable to make chain rpc: %v", err)
×
3189
                        }
×
3190

3191
                case "neutrino":
1✔
3192
                        // Set some package-level variable to speed up
1✔
3193
                        // operation for tests.
1✔
3194
                        neutrino.BanDuration = time.Millisecond * 100
1✔
3195
                        neutrino.QueryTimeout = time.Millisecond * 500
1✔
3196
                        neutrino.QueryNumRetries = 1
1✔
3197

1✔
3198
                        // Start Alice - open a database, start a neutrino
1✔
3199
                        // instance, and initialize a btcwallet driver for it.
1✔
3200
                        aliceDB, err := walletdb.Create(
1✔
3201
                                "bdb", tempTestDirAlice+"/neutrino.db", true,
1✔
3202
                                kvdb.DefaultDBTimeout,
1✔
3203
                        )
1✔
3204
                        if err != nil {
1✔
3205
                                t.Fatalf("unable to create DB: %v", err)
×
3206
                        }
×
3207
                        defer aliceDB.Close()
1✔
3208
                        aliceChain, err := neutrino.NewChainService(
1✔
3209
                                neutrino.Config{
1✔
3210
                                        DataDir:     tempTestDirAlice,
1✔
3211
                                        Database:    aliceDB,
1✔
3212
                                        ChainParams: *netParams,
1✔
3213
                                        ConnectPeers: []string{
1✔
3214
                                                miningNode.P2PAddress(),
1✔
3215
                                        },
1✔
3216
                                },
1✔
3217
                        )
1✔
3218
                        if err != nil {
1✔
3219
                                t.Fatalf("unable to make neutrino: %v", err)
×
3220
                        }
×
3221
                        aliceChain.Start()
1✔
3222
                        defer aliceChain.Stop()
1✔
3223
                        aliceClient = chain.NewNeutrinoClient(
1✔
3224
                                netParams, aliceChain,
1✔
3225
                        )
1✔
3226

1✔
3227
                        // Start Bob - open a database, start a neutrino
1✔
3228
                        // instance, and initialize a btcwallet driver for it.
1✔
3229
                        bobDB, err := walletdb.Create(
1✔
3230
                                "bdb", tempTestDirBob+"/neutrino.db", true,
1✔
3231
                                kvdb.DefaultDBTimeout,
1✔
3232
                        )
1✔
3233
                        if err != nil {
1✔
3234
                                t.Fatalf("unable to create DB: %v", err)
×
3235
                        }
×
3236
                        defer bobDB.Close()
1✔
3237
                        bobChain, err := neutrino.NewChainService(
1✔
3238
                                neutrino.Config{
1✔
3239
                                        DataDir:     tempTestDirBob,
1✔
3240
                                        Database:    bobDB,
1✔
3241
                                        ChainParams: *netParams,
1✔
3242
                                        ConnectPeers: []string{
1✔
3243
                                                miningNode.P2PAddress(),
1✔
3244
                                        },
1✔
3245
                                },
1✔
3246
                        )
1✔
3247
                        if err != nil {
1✔
3248
                                t.Fatalf("unable to make neutrino: %v", err)
×
3249
                        }
×
3250
                        bobChain.Start()
1✔
3251
                        defer bobChain.Stop()
1✔
3252
                        bobClient = chain.NewNeutrinoClient(
1✔
3253
                                netParams, bobChain,
1✔
3254
                        )
1✔
3255

3256
                case "bitcoind":
1✔
3257
                        // Start a bitcoind instance.
1✔
3258
                        chainConn := unittest.NewBitcoindBackend(
1✔
3259
                                t, unittest.NetParams, miningNode.P2PAddress(),
1✔
3260
                                true, false,
1✔
3261
                        )
1✔
3262

1✔
3263
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3264
                        // Bob.
1✔
3265
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3266
                        bobClient = chainConn.NewBitcoindClient()
1✔
3267

3268
                case "bitcoind-rpc-polling":
1✔
3269
                        // Start a bitcoind instance.
1✔
3270
                        chainConn := unittest.NewBitcoindBackend(
1✔
3271
                                t, unittest.NetParams, miningNode.P2PAddress(),
1✔
3272
                                true, true,
1✔
3273
                        )
1✔
3274

1✔
3275
                        // Create a btcwallet bitcoind client for both Alice and
1✔
3276
                        // Bob.
1✔
3277
                        aliceClient = chainConn.NewBitcoindClient()
1✔
3278
                        bobClient = chainConn.NewBitcoindClient()
1✔
3279

3280
                default:
×
3281
                        t.Fatalf("unknown chain driver: %v", backEnd)
×
3282
                }
3283

3284
                aliceSeed := sha256.New()
4✔
3285
                aliceSeed.Write([]byte(backEnd))
4✔
3286
                aliceSeed.Write(aliceHDSeed[:])
4✔
3287
                aliceSeedBytes := aliceSeed.Sum(nil)
4✔
3288

4✔
3289
                aliceWalletConfig := &btcwallet.Config{
4✔
3290
                        PrivatePass: []byte("alice-pass"),
4✔
3291
                        HdSeed:      aliceSeedBytes,
4✔
3292
                        NetParams:   netParams,
4✔
3293
                        ChainSource: aliceClient,
4✔
3294
                        CoinType:    keychain.CoinTypeTestnet,
4✔
3295
                        // wallet starts in recovery mode
4✔
3296
                        RecoveryWindow: 2,
4✔
3297
                        LoaderOptions: []btcwallet.LoaderOption{
4✔
3298
                                btcwallet.LoaderWithLocalWalletDB(
4✔
3299
                                        tempTestDirAlice, false, time.Minute,
4✔
3300
                                ),
4✔
3301
                        },
4✔
3302
                }
4✔
3303
                aliceWalletController, err = walletDriver.New(
4✔
3304
                        aliceWalletConfig, blockCache,
4✔
3305
                )
4✔
3306
                if err != nil {
4✔
3307
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3308
                }
×
3309
                aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
4✔
3310
                aliceKeyRing = keychain.NewBtcWalletKeyRing(
4✔
3311
                        aliceWalletController.(*btcwallet.BtcWallet).InternalWallet(),
4✔
3312
                        keychain.CoinTypeTestnet,
4✔
3313
                )
4✔
3314

4✔
3315
                bobSeed := sha256.New()
4✔
3316
                bobSeed.Write([]byte(backEnd))
4✔
3317
                bobSeed.Write(bobHDSeed[:])
4✔
3318
                bobSeedBytes := bobSeed.Sum(nil)
4✔
3319

4✔
3320
                bobWalletConfig := &btcwallet.Config{
4✔
3321
                        PrivatePass: []byte("bob-pass"),
4✔
3322
                        HdSeed:      bobSeedBytes,
4✔
3323
                        NetParams:   netParams,
4✔
3324
                        ChainSource: bobClient,
4✔
3325
                        CoinType:    keychain.CoinTypeTestnet,
4✔
3326
                        // wallet starts without recovery mode
4✔
3327
                        RecoveryWindow: 0,
4✔
3328
                        LoaderOptions: []btcwallet.LoaderOption{
4✔
3329
                                btcwallet.LoaderWithLocalWalletDB(
4✔
3330
                                        tempTestDirBob, false, time.Minute,
4✔
3331
                                ),
4✔
3332
                        },
4✔
3333
                }
4✔
3334
                bobWalletController, err = walletDriver.New(
4✔
3335
                        bobWalletConfig, blockCache,
4✔
3336
                )
4✔
3337
                if err != nil {
4✔
3338
                        t.Fatalf("unable to create btcwallet: %v", err)
×
3339
                }
×
3340
                bobSigner = bobWalletController.(*btcwallet.BtcWallet)
4✔
3341
                bobKeyRing = keychain.NewBtcWalletKeyRing(
4✔
3342
                        bobWalletController.(*btcwallet.BtcWallet).InternalWallet(),
4✔
3343
                        keychain.CoinTypeTestnet,
4✔
3344
                )
4✔
3345
                bio = bobWalletController.(*btcwallet.BtcWallet)
4✔
3346
        default:
×
3347
                t.Fatalf("unknown wallet driver: %v", walletType)
×
3348
        }
3349

3350
        // Funding via 20 outputs with 4BTC each.
3351
        alice, err := createTestWallet(
4✔
3352
                tempTestDirAlice, miningNode, netParams,
4✔
3353
                chainNotifier, aliceWalletController, aliceKeyRing,
4✔
3354
                aliceSigner, bio,
4✔
3355
        )
4✔
3356
        require.NoError(t, err, "unable to create test ln wallet")
4✔
3357
        defer alice.Shutdown()
4✔
3358

4✔
3359
        bob, err := createTestWallet(
4✔
3360
                tempTestDirBob, miningNode, netParams,
4✔
3361
                chainNotifier, bobWalletController, bobKeyRing, bobSigner, bio,
4✔
3362
        )
4✔
3363
        require.NoError(t, err, "unable to create test ln wallet")
4✔
3364
        defer bob.Shutdown()
4✔
3365

4✔
3366
        // Both wallets should now have 80BTC available for
4✔
3367
        // spending.
4✔
3368
        assertProperBalance(t, alice, 1, 80)
4✔
3369
        assertProperBalance(t, bob, 1, 80)
4✔
3370

4✔
3371
        // Execute every test, clearing possibly mutated
4✔
3372
        // wallet state after each step.
4✔
3373
        for _, walletTest := range walletTests {
88✔
3374

84✔
3375
                walletTest := walletTest
84✔
3376

84✔
3377
                testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
84✔
3378
                        walletTest.name)
84✔
3379
                success := t.Run(testName, func(t *testing.T) {
168✔
3380
                        if backEnd == "neutrino" &&
84✔
3381
                                strings.Contains(walletTest.name, "dual funder") {
85✔
3382

1✔
3383
                                t.Skip("skipping dual funder tests for neutrino")
1✔
3384
                        }
1✔
3385
                        if backEnd == "neutrino" &&
83✔
3386
                                strings.Contains(walletTest.name, "spend unconfirmed") {
84✔
3387

1✔
3388
                                t.Skip("skipping spend unconfirmed tests for neutrino")
1✔
3389
                        }
1✔
3390

3391
                        walletTest.test(miningNode, alice, bob, t)
82✔
3392
                })
3393
                if !success {
84✔
3394
                        return false
×
3395
                }
×
3396

3397
                // TODO(roasbeef): possible reset mining
3398
                // node's chainstate to initial level, cleanly
3399
                // wipe buckets
3400
                if err := clearWalletStates(alice, bob); err !=
84✔
3401
                        nil && err != kvdb.ErrBucketNotFound {
84✔
3402

×
3403
                        t.Fatalf("unable to wipe wallet state: %v", err)
×
3404
                }
×
3405
        }
3406

3407
        return true
4✔
3408
}
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