multi: add bitcoind drivers and tests
This commit is contained in:
1 parent
244ae4b571
commit
187f59556a
13 files changed
+2138
-241
No files matched your search
@@ -29,23 +29,7 @@ var (
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBestBlock() (*chainhash.Hash, int32, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
header, height, err := backend.CS.BlockHeaders.ChainTip()
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
blockHash := header.BlockHash()
|
||||
return &blockHash, int32(height), nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
return backend.GetBestBlock()
|
||||
|
||||
default:
|
||||
return nil, -1, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBestBlock()
|
||||
}
|
||||
|
||||
// GetUtxo returns the original output referenced by the passed outpoint.
|
||||
@@ -100,6 +84,26 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut,
|
||||
PkScript: pkScript,
|
||||
}, nil
|
||||
|
||||
case *chain.BitcoindClient:
|
||||
txout, err := backend.GetTxOut(&op.Hash, op.Index, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if txout == nil {
|
||||
return nil, ErrOutputSpent
|
||||
}
|
||||
|
||||
pkScript, err := hex.DecodeString(txout.ScriptPubKey.Hex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wire.TxOut{
|
||||
// Sadly, gettxout returns the output value in BTC
|
||||
// instead of satoshis.
|
||||
Value: int64(txout.Value * 1e8),
|
||||
PkScript: pkScript,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
@@ -109,27 +113,7 @@ func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut,
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
block, err := backend.CS.GetBlockFromNetwork(*blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block.MsgBlock(), nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
block, err := backend.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return block, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBlock(blockHash)
|
||||
}
|
||||
|
||||
// GetBlockHash returns the hash of the block in the best blockchain at the
|
||||
@@ -137,29 +121,7 @@ func (b *BtcWallet) GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error)
|
||||
//
|
||||
// This method is a part of the lnwallet.BlockChainIO interface.
|
||||
func (b *BtcWallet) GetBlockHash(blockHeight int64) (*chainhash.Hash, error) {
|
||||
switch backend := b.chain.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
height := uint32(blockHeight)
|
||||
blockHeader, err := backend.CS.BlockHeaders.FetchHeaderByHeight(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockHash := blockHeader.BlockHash()
|
||||
return &blockHash, nil
|
||||
|
||||
case *chain.RPCClient:
|
||||
blockHash, err := backend.GetBlockHash(blockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blockHash, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend")
|
||||
}
|
||||
return b.chain.GetBlockHash(blockHeight)
|
||||
}
|
||||
|
||||
// A compile time check to ensure that BtcWallet implements the BlockChainIO
|
||||
|
||||
@@ -119,6 +119,17 @@ func New(cfg Config) (*BtcWallet, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BackEnd returns the underlying ChainService's name as a string.
|
||||
//
|
||||
// This is a part of the WalletController interface.
|
||||
func (b *BtcWallet) BackEnd() string {
|
||||
if b.chain != nil {
|
||||
return b.chain.BackEnd()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Start initializes the underlying rpc connection, the wallet itself, and
|
||||
// begins syncing to the current available blockchain state.
|
||||
//
|
||||
@@ -668,22 +679,9 @@ func (b *BtcWallet) IsSynced() (bool, error) {
|
||||
|
||||
// Next, query the chain backend to grab the info about the tip of the
|
||||
// main chain.
|
||||
switch backend := b.cfg.ChainSource.(type) {
|
||||
case *chain.NeutrinoClient:
|
||||
header, height, err := backend.CS.BlockHeaders.ChainTip()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
bh := header.BlockHash()
|
||||
bestHash = &bh
|
||||
bestHeight = int32(height)
|
||||
|
||||
case *chain.RPCClient:
|
||||
bestHash, bestHeight, err = backend.GetBestBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bestHash, bestHeight, err = b.cfg.ChainSource.GetBestBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If the wallet hasn't yet fully synced to the node's best chain tip,
|
||||
@@ -696,21 +694,9 @@ func (b *BtcWallet) IsSynced() (bool, error) {
|
||||
// still may not yet be synced as the chain backend may still be
|
||||
// catching up to the main chain. So we'll grab the block header in
|
||||
// order to make a guess based on the current time stamp.
|
||||
var blockHeader *wire.BlockHeader
|
||||
switch backend := b.cfg.ChainSource.(type) {
|
||||
|
||||
case *chain.NeutrinoClient:
|
||||
bh, _, err := backend.CS.BlockHeaders.FetchHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
blockHeader = bh
|
||||
|
||||
case *chain.RPCClient:
|
||||
blockHeader, err = backend.GetBlockHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
blockHeader, err := b.cfg.ChainSource.GetBlockHeader(bestHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// If the timestamp no the best header is more than 2 hours in the
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -36,6 +37,7 @@ func init() {
|
||||
driver := &lnwallet.WalletDriver{
|
||||
WalletType: walletType,
|
||||
New: createNewWallet,
|
||||
BackEnds: chain.BackEnds,
|
||||
}
|
||||
|
||||
if err := lnwallet.RegisterWallet(driver); err != nil {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/roasbeef/btcd/blockchain"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
"github.com/roasbeef/btcutil"
|
||||
@@ -200,3 +202,145 @@ func (b *BtcdFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amou
|
||||
// A compile-time assertion to ensure that BtcdFeeEstimator implements the
|
||||
// FeeEstimator interface.
|
||||
var _ FeeEstimator = (*BtcdFeeEstimator)(nil)
|
||||
|
||||
// BitcoindFeeEstimator is an implementation of the FeeEstimator interface
|
||||
// backed by the RPC interface of an active bitcoind node. This implementation
|
||||
// will proxy any fee estimation requests to bitcoind's RPC interace.
|
||||
type BitcoindFeeEstimator struct {
|
||||
// fallBackFeeRate is the fall back fee rate in satoshis per byte that
|
||||
// is returned if the fee estimator does not yet have enough data to
|
||||
// actually produce fee estimates.
|
||||
fallBackFeeRate btcutil.Amount
|
||||
|
||||
bitcoindConn *rpcclient.Client
|
||||
}
|
||||
|
||||
// NewBitcoindFeeEstimator creates a new BitcoindFeeEstimator given a fully
|
||||
// populated rpc config that is able to successfully connect and authenticate
|
||||
// with the bitcoind node, and also a fall back fee rate. The fallback fee rate
|
||||
// is used in the occasion that the estimator has insufficient data, or returns
|
||||
// zero for a fee estimate.
|
||||
func NewBitcoindFeeEstimator(rpcConfig rpcclient.ConnConfig,
|
||||
fallBackFeeRate btcutil.Amount) (*BitcoindFeeEstimator, error) {
|
||||
|
||||
rpcConfig.DisableConnectOnNew = true
|
||||
rpcConfig.DisableAutoReconnect = false
|
||||
rpcConfig.DisableTLS = true
|
||||
rpcConfig.HTTPPostMode = true
|
||||
chainConn, err := rpcclient.New(&rpcConfig, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BitcoindFeeEstimator{
|
||||
fallBackFeeRate: fallBackFeeRate,
|
||||
bitcoindConn: chainConn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start signals the FeeEstimator to start any processes or goroutines
|
||||
// it needs to perform its duty.
|
||||
//
|
||||
// NOTE: This method is part of the FeeEstimator interface.
|
||||
func (b *BitcoindFeeEstimator) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops any spawned goroutines and cleans up the resources used
|
||||
// by the fee estimator.
|
||||
//
|
||||
// NOTE: This method is part of the FeeEstimator interface.
|
||||
func (b *BitcoindFeeEstimator) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EstimateFeePerByte takes in a target for the number of blocks until an
|
||||
// initial confirmation and returns the estimated fee expressed in
|
||||
// satoshis/byte.
|
||||
func (b *BitcoindFeeEstimator) EstimateFeePerByte(numBlocks uint32) (btcutil.Amount, error) {
|
||||
feeEstimate, err := b.fetchEstimatePerByte(numBlocks)
|
||||
switch {
|
||||
// If the estimator doesn't have enough data, or returns an error, then
|
||||
// to return a proper value, then we'll return the default fall back
|
||||
// fee rate.
|
||||
case err != nil:
|
||||
walletLog.Errorf("unable to query estimator: %v", err)
|
||||
fallthrough
|
||||
|
||||
case feeEstimate == 0:
|
||||
return b.fallBackFeeRate, nil
|
||||
}
|
||||
|
||||
return feeEstimate, nil
|
||||
}
|
||||
|
||||
// EstimateFeePerWeight takes in a target for the number of blocks until an
|
||||
// initial confirmation and returns the estimated fee expressed in
|
||||
// satoshis/weight.
|
||||
func (b *BitcoindFeeEstimator) EstimateFeePerWeight(numBlocks uint32) (btcutil.Amount, error) {
|
||||
feePerByte, err := b.EstimateFeePerByte(numBlocks)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// We'll scale down the fee per byte to fee per weight, as for each raw
|
||||
// byte, there's 1/4 unit of weight mapped to it.
|
||||
satWeight := feePerByte / blockchain.WitnessScaleFactor
|
||||
|
||||
// If this ends up scaling down to a zero sat/weight amount, then we'll
|
||||
// use the default fallback fee rate.
|
||||
// TODO(aakselrod): maybe use the per-byte rate if it's non-zero?
|
||||
// Otherwise, we can return a higher sat/byte than sat/weight.
|
||||
if satWeight == 0 {
|
||||
return b.fallBackFeeRate / blockchain.WitnessScaleFactor, nil
|
||||
}
|
||||
|
||||
return satWeight, nil
|
||||
}
|
||||
|
||||
// fetchEstimate returns a fee estimate for a transaction be be confirmed in
|
||||
// confTarget blocks. The estimate is returned in sat/byte.
|
||||
func (b *BitcoindFeeEstimator) fetchEstimatePerByte(confTarget uint32) (btcutil.Amount, error) {
|
||||
// First, we'll send an "estimatesmartfee" command as a raw request,
|
||||
// since it isn't supported by btcd but is available in bitcoind.
|
||||
target, err := json.Marshal(uint64(confTarget))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO: Allow selection of economical/conservative modifiers.
|
||||
resp, err := b.bitcoindConn.RawRequest("estimatesmartfee",
|
||||
[]json.RawMessage{target})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll parse the response to get the BTC per KB.
|
||||
feeEstimate := struct {
|
||||
Feerate float64 `json:"feerate"`
|
||||
}{}
|
||||
err = json.Unmarshal(resp, &feeEstimate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Next, we'll convert the returned value to satoshis, as it's
|
||||
// currently returned in BTC.
|
||||
satPerKB, err := btcutil.NewAmount(feeEstimate.Feerate)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// The value returned is expressed in fees per KB, while we want
|
||||
// fee-per-byte, so we'll divide by 1024 to map to satoshis-per-byte
|
||||
// before returning the estimate.
|
||||
satPerByte := satPerKB / 1024
|
||||
|
||||
walletLog.Debugf("Returning %v sat/byte for conf target of %v",
|
||||
int64(satPerByte), confTarget)
|
||||
|
||||
return satPerByte, nil
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that BitcoindFeeEstimator implements the
|
||||
// FeeEstimator interface.
|
||||
var _ FeeEstimator = (*BitcoindFeeEstimator)(nil)
|
||||
@@ -207,6 +207,11 @@ type WalletController interface {
|
||||
// Stop signals the wallet for shutdown. Shutdown may entail closing
|
||||
// any active sockets, database handles, stopping goroutines, etc.
|
||||
Stop() error
|
||||
|
||||
// BackEnd returns a name for the wallet's backing chain service,
|
||||
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
|
||||
// service.
|
||||
BackEnd() string
|
||||
}
|
||||
|
||||
// BlockChainIO is a dedicated source which will be used to obtain queries
|
||||
@@ -288,6 +293,10 @@ type WalletDriver struct {
|
||||
// initialization flexibility, thereby accommodating several potential
|
||||
// WalletController implementations.
|
||||
New func(args ...interface{}) (WalletController, error)
|
||||
|
||||
// BackEnds returns a list of available chain service drivers for the
|
||||
// wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc.
|
||||
BackEnds func() []string
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
+427
-140
@@ -5,8 +5,10 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -17,7 +19,10 @@ import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/roasbeef/btcwallet/chain"
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
||||
@@ -25,10 +30,10 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/roasbeef/btcd/btcjson"
|
||||
"github.com/roasbeef/btcd/chaincfg"
|
||||
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||
"github.com/roasbeef/btcd/rpcclient"
|
||||
_ "github.com/roasbeef/btcwallet/walletdb/bdb"
|
||||
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcd/integration/rpctest"
|
||||
@@ -76,7 +81,7 @@ var (
|
||||
0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
|
||||
}
|
||||
|
||||
netParams = &chaincfg.SimNetParams
|
||||
netParams = &chaincfg.RegressionNetParams
|
||||
chainHash = netParams.GenesisHash
|
||||
|
||||
_, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), testHdSeed[:])
|
||||
@@ -145,6 +150,12 @@ func calcStaticFee(numHTLCs int) btcutil.Amount {
|
||||
func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
numOutputs, btcPerOutput int) error {
|
||||
|
||||
// For initial neutrino connection, wait a second.
|
||||
// TODO(aakselrod): Eliminate the need for this.
|
||||
switch w.BackEnd() {
|
||||
case "neutrino":
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
// Using the mining node, spend from a coinbase output numOutputs to
|
||||
// give us btcPerOutput with each output.
|
||||
satoshiPerOutput := int64(btcPerOutput * 1e8)
|
||||
@@ -188,6 +199,7 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
|
||||
// Wait until the wallet has finished syncing up to the main chain.
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
timeout := time.After(30 * time.Second)
|
||||
|
||||
for range ticker.C {
|
||||
balance, err := w.ConfirmedBalance(1, false)
|
||||
@@ -197,6 +209,17 @@ func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
if balance == expectedBalance {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-timeout:
|
||||
synced, err := w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("timed out after 30 seconds "+
|
||||
"waiting for balance %v, current balance %v, "+
|
||||
"synced: %t", expectedBalance, balance, synced)
|
||||
default:
|
||||
}
|
||||
}
|
||||
ticker.Stop()
|
||||
|
||||
@@ -222,7 +245,7 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
|
||||
WalletController: wc,
|
||||
Signer: signer,
|
||||
ChainIO: bio,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 10},
|
||||
DefaultConstraints: channeldb.ChannelConstraints{
|
||||
DustLimit: 500,
|
||||
MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100,
|
||||
@@ -343,6 +366,9 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
bobFundingSigs, bobCommitSig,
|
||||
)
|
||||
if err != nil {
|
||||
for _, in := range aliceChanReservation.FinalFundingTx().TxIn {
|
||||
fmt.Println(in.PreviousOutPoint.String())
|
||||
}
|
||||
t.Fatalf("unable to consume alice's sigs: %v", err)
|
||||
}
|
||||
_, err = bobChanReservation.CompleteReservation(
|
||||
@@ -384,6 +410,10 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
// Mine a single block, the funding transaction should be included
|
||||
// within this block.
|
||||
err = waitForMempoolTx(miner, &fundingSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
blockHashes, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@@ -402,6 +432,16 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
assertReservationDeleted(aliceChanReservation, t)
|
||||
assertReservationDeleted(bobChanReservation, t)
|
||||
|
||||
// Wait for wallets to catch up to prevent issues in subsequent tests.
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync alice: %v", err)
|
||||
}
|
||||
err = waitForWalletSync(miner, bob)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync bob: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
|
||||
@@ -759,6 +799,10 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
|
||||
// Mine a single block, the funding transaction should be included
|
||||
// within this block.
|
||||
err = waitForMempoolTx(miner, &fundingSha)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
blockHashes, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate block: %v", err)
|
||||
@@ -768,7 +812,8 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
t.Fatalf("unable to find block: %v", err)
|
||||
}
|
||||
if len(block.Transactions) != 2 {
|
||||
t.Fatalf("funding transaction wasn't mined: %v", err)
|
||||
t.Fatalf("funding transaction wasn't mined: %d",
|
||||
len(block.Transactions))
|
||||
}
|
||||
blockTx := block.Transactions[1]
|
||||
if blockTx.TxHash() != fundingSha {
|
||||
@@ -815,8 +860,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
}
|
||||
|
||||
// Next, fetch all the current transaction details.
|
||||
// TODO(roasbeef): use ntfn client here instead?
|
||||
time.Sleep(time.Second * 2)
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
|
||||
}
|
||||
txDetails, err := alice.ListTransactionDetails()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch tx details: %v", err)
|
||||
@@ -905,6 +952,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create burn tx: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(miner, burnTXID)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
burnBlock, err := miner.Node.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mine block: %v", err)
|
||||
@@ -912,7 +963,10 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
||||
|
||||
// Fetch the transaction details again, the new transaction should be
|
||||
// shown as debiting from the wallet's balance.
|
||||
time.Sleep(time.Second * 2)
|
||||
err = waitForWalletSync(miner, alice)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't sync Alice's wallet: %v", err)
|
||||
}
|
||||
txDetails, err = alice.ListTransactionDetails()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch tx details: %v", err)
|
||||
@@ -955,7 +1009,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
// implementation of the WalletController.
|
||||
txClient, err := alice.SubscribeTransactions()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate tx subscription: %v", err)
|
||||
t.Skipf("unable to generate tx subscription: %v", err)
|
||||
}
|
||||
defer txClient.Cancel()
|
||||
|
||||
@@ -964,25 +1018,33 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
numTxns = 3
|
||||
)
|
||||
unconfirmedNtfns := make(chan struct{})
|
||||
go func() {
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.UnconfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 0 {
|
||||
t.Fatalf("incorrect number of confs, expected %v got %v",
|
||||
0, txDetail.NumConfirmations)
|
||||
switch alice.BackEnd() {
|
||||
case "neutrino":
|
||||
// Neutrino doesn't listen for unconfirmed transactions.
|
||||
default:
|
||||
go func() {
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.UnconfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 0 {
|
||||
t.Fatalf("incorrect number of confs, "+
|
||||
"expected %v got %v", 0,
|
||||
txDetail.NumConfirmations)
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, "+
|
||||
"expected %v got %v", outputAmt,
|
||||
txDetail.Value)
|
||||
}
|
||||
if txDetail.BlockHash != nil {
|
||||
t.Fatalf("block hash should be nil, "+
|
||||
"is instead %v",
|
||||
txDetail.BlockHash)
|
||||
}
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, expected %v got %v",
|
||||
outputAmt, txDetail.Value)
|
||||
}
|
||||
if txDetail.BlockHash != nil {
|
||||
t.Fatalf("block hash should be nil, is instead %v",
|
||||
txDetail.BlockHash)
|
||||
}
|
||||
}
|
||||
|
||||
close(unconfirmedNtfns)
|
||||
}()
|
||||
close(unconfirmedNtfns)
|
||||
}()
|
||||
}
|
||||
|
||||
// Next, fetch a fresh address from the wallet, create 3 new outputs
|
||||
// with the pkScript.
|
||||
@@ -1000,17 +1062,27 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
Value: outputAmt,
|
||||
PkScript: script,
|
||||
}
|
||||
if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
|
||||
txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send coinbase: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(miner, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We should receive a notification for all three transactions
|
||||
// generated above.
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("transactions not received after 3 seconds")
|
||||
case <-unconfirmedNtfns: // Fall through on successs
|
||||
switch alice.BackEnd() {
|
||||
case "neutrino":
|
||||
// Neutrino doesn't listen for on unconfirmed transactions.
|
||||
default:
|
||||
// We should receive a notification for all three transactions
|
||||
// generated above.
|
||||
select {
|
||||
case <-time.After(time.Second * 10):
|
||||
t.Fatalf("transactions not received after 10 seconds")
|
||||
case <-unconfirmedNtfns: // Fall through on successs
|
||||
}
|
||||
}
|
||||
|
||||
confirmedNtfns := make(chan struct{})
|
||||
@@ -1018,12 +1090,12 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
for i := 0; i < numTxns; i++ {
|
||||
txDetail := <-txClient.ConfirmedTransactions()
|
||||
if txDetail.NumConfirmations != 1 {
|
||||
t.Fatalf("incorrect number of confs, expected %v got %v",
|
||||
1, txDetail.NumConfirmations)
|
||||
t.Fatalf("incorrect number of confs for %s, expected %v got %v",
|
||||
txDetail.Hash, 1, txDetail.NumConfirmations)
|
||||
}
|
||||
if txDetail.Value != outputAmt {
|
||||
t.Fatalf("incorrect output amt, expected %v got %v",
|
||||
outputAmt, txDetail.Value)
|
||||
t.Fatalf("incorrect output amt, expected %v got %v in txid %s",
|
||||
outputAmt, txDetail.Value, txDetail.Hash)
|
||||
}
|
||||
}
|
||||
close(confirmedNtfns)
|
||||
@@ -1039,7 +1111,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
|
||||
// since they should be mined in the next block.
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
t.Fatalf("transactions not received after 3 seconds")
|
||||
t.Fatalf("transactions not received after 5 seconds")
|
||||
case <-confirmedNtfns: // Fall through on success
|
||||
}
|
||||
}
|
||||
@@ -1088,7 +1160,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
|
||||
// generate a regular p2wkh from that.
|
||||
pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
|
||||
keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
|
||||
&chaincfg.SimNetParams)
|
||||
&chaincfg.RegressionNetParams)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create addr: %v", err)
|
||||
}
|
||||
@@ -1110,6 +1182,10 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
|
||||
|
||||
// Query for the transaction generated above so we can located
|
||||
// the index of our output.
|
||||
err = waitForMempoolTx(r, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
tx, err := r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for tx: %v", err)
|
||||
@@ -1197,7 +1273,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@@ -1222,16 +1298,21 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
Value: 1e8,
|
||||
PkScript: script,
|
||||
}
|
||||
if _, err = w.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
|
||||
txid, err := w.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send outputs: %v", err)
|
||||
}
|
||||
err = waitForMempoolTx(r, txid)
|
||||
if err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
_, err = r.Node.Generate(50)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate blocks on passed node: %v", err)
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@@ -1277,32 +1358,34 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
// one block on the passed miner and two on the created miner,
|
||||
// connecting them, and waiting for them to sync.
|
||||
for i := 0; i < 5; i++ {
|
||||
peers, err := r2.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get peer info: %v", err)
|
||||
}
|
||||
numPeers := len(peers)
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANRemove)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to disconnect mining nodes: %v", err)
|
||||
}
|
||||
// Wait for disconnection
|
||||
timeout := time.After(30 * time.Second)
|
||||
for true {
|
||||
stillConnected := true
|
||||
var peers []btcjson.GetPeerInfoResult
|
||||
for stillConnected {
|
||||
// Allow for timeout
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatalf("timeout waiting for miner disconnect")
|
||||
default:
|
||||
}
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANRemove)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to disconnect mining nodes: %v",
|
||||
err)
|
||||
}
|
||||
peers, err = r2.Node.GetPeerInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get peer info: %v", err)
|
||||
}
|
||||
if len(peers) < numPeers {
|
||||
break
|
||||
stillConnected = false
|
||||
for _, peer := range peers {
|
||||
if peer.Addr == r.P2PAddress() {
|
||||
stillConnected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
_, err = r.Node.Generate(2)
|
||||
if err != nil {
|
||||
@@ -1318,8 +1401,16 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
// Step 5: Reconnect the miners and wait for them to synchronize.
|
||||
err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANAdd)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect mining nodes together: %v",
|
||||
err)
|
||||
switch err := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if err.Code != -8 {
|
||||
t.Fatalf("unable to connect mining "+
|
||||
"nodes together: %v", err)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unable to connect mining nodes "+
|
||||
"together: %v", err)
|
||||
}
|
||||
}
|
||||
err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
|
||||
rpctest.Blocks)
|
||||
@@ -1328,7 +1419,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
}
|
||||
|
||||
// Give wallet time to catch up.
|
||||
err = waitForWalletSync(w)
|
||||
err = waitForWalletSync(r, w)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sync wallet: %v", err)
|
||||
}
|
||||
@@ -1405,21 +1496,78 @@ func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
||||
return b.Cfg.Database.Wipe()
|
||||
}
|
||||
|
||||
func waitForWalletSync(w *lnwallet.LightningWallet) error {
|
||||
var synced bool
|
||||
func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error {
|
||||
var found bool
|
||||
var tx *btcutil.Tx
|
||||
var err error
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !synced {
|
||||
synced, err = w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for !found {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check for the harness' knowledge of the txid
|
||||
tx, err = r.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
switch e := err.(type) {
|
||||
case *btcjson.RPCError:
|
||||
if e.Code == btcjson.ErrRPCNoTxInfo {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tx != nil && tx.MsgTx().TxHash() == *txid {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
|
||||
var synced bool
|
||||
var err error
|
||||
var bestHash, knownHash *chainhash.Hash
|
||||
var bestHeight, knownHeight int32
|
||||
timeout := time.After(10 * time.Second)
|
||||
for !synced {
|
||||
// Do a short wait
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timeout after 10s")
|
||||
default:
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check whether the chain source of the wallet is caught up to
|
||||
// the harness it's supposed to be catching up to.
|
||||
bestHash, bestHeight, err = r.Node.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
knownHash, knownHeight, err = w.Cfg.ChainIO.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if knownHeight != bestHeight {
|
||||
continue
|
||||
}
|
||||
if *knownHash != *bestHash {
|
||||
return fmt.Errorf("hash at height %d doesn't match: "+
|
||||
"expected %s, got %s", bestHeight, bestHash,
|
||||
knownHash)
|
||||
}
|
||||
|
||||
// Check for synchronization.
|
||||
synced, err = w.IsSynced()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1454,7 +1602,7 @@ func TestLightningWallet(t *testing.T) {
|
||||
}
|
||||
|
||||
// Next mine enough blocks in order for segwit and the CSV package
|
||||
// soft-fork to activate on SimNet.
|
||||
// soft-fork to activate on RegNet.
|
||||
numBlocks := netParams.MinerConfirmationWindow * 2
|
||||
if _, err := miningNode.Node.Generate(numBlocks); err != nil {
|
||||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
@@ -1470,6 +1618,22 @@ func TestLightningWallet(t *testing.T) {
|
||||
t.Fatalf("unable to start notifier: %v", err)
|
||||
}
|
||||
|
||||
for _, walletDriver := range lnwallet.RegisteredWallets() {
|
||||
for _, backEnd := range walletDriver.BackEnds() {
|
||||
runTests(t, walletDriver, backEnd, miningNode,
|
||||
rpcConfig, chainNotifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runTests runs all of the tests for a single interface implementation and
|
||||
// chain back-end combination. This makes it easier to use `defer` as well as
|
||||
// factoring out the test logic from the loop which cycles through the
|
||||
// interface implementations.
|
||||
func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
||||
backEnd string, miningNode *rpctest.Harness,
|
||||
rpcConfig rpcclient.ConnConfig,
|
||||
chainNotifier *btcdnotify.BtcdNotifier) {
|
||||
var (
|
||||
bio lnwallet.BlockChainIO
|
||||
|
||||
@@ -1478,107 +1642,230 @@ func TestLightningWallet(t *testing.T) {
|
||||
|
||||
aliceWalletController lnwallet.WalletController
|
||||
bobWalletController lnwallet.WalletController
|
||||
|
||||
feeEstimator lnwallet.FeeEstimator
|
||||
)
|
||||
for _, walletDriver := range lnwallet.RegisteredWallets() {
|
||||
tempTestDirAlice, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirAlice)
|
||||
|
||||
tempTestDirBob, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirBob)
|
||||
tempTestDirAlice, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirAlice)
|
||||
|
||||
walletType := walletDriver.WalletType
|
||||
switch walletType {
|
||||
case "btcwallet":
|
||||
aliceChainRPC, err := chain.NewRPCClient(netParams,
|
||||
tempTestDirBob, err := ioutil.TempDir("", "lnwallet")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempTestDirBob)
|
||||
|
||||
walletType := walletDriver.WalletType
|
||||
switch walletType {
|
||||
case "btcwallet":
|
||||
var aliceClient, bobClient chain.Interface
|
||||
switch backEnd {
|
||||
case "btcd":
|
||||
feeEstimator, err = lnwallet.NewBtcdFeeEstimator(
|
||||
rpcConfig, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcd fee estimator: %v",
|
||||
err)
|
||||
}
|
||||
aliceClient, err = chain.NewRPCClient(netParams,
|
||||
rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
|
||||
rpcConfig.Certificates, false, 20)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make chain rpc: %v", err)
|
||||
}
|
||||
aliceWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("alice-pass"),
|
||||
HdSeed: aliceHDSeed[:],
|
||||
DataDir: tempTestDirAlice,
|
||||
NetParams: netParams,
|
||||
ChainSource: aliceChainRPC,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
}
|
||||
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
|
||||
|
||||
bobChainRPC, err := chain.NewRPCClient(netParams,
|
||||
bobClient, err = chain.NewRPCClient(netParams,
|
||||
rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
|
||||
rpcConfig.Certificates, false, 20)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make chain rpc: %v", err)
|
||||
}
|
||||
bobWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("bob-pass"),
|
||||
HdSeed: bobHDSeed[:],
|
||||
DataDir: tempTestDirBob,
|
||||
NetParams: netParams,
|
||||
ChainSource: bobChainRPC,
|
||||
FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
|
||||
}
|
||||
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
||||
case "neutrino":
|
||||
feeEstimator = lnwallet.StaticFeeEstimator{FeeRate: 250}
|
||||
// Set some package-level variable to speed up
|
||||
// operation for tests.
|
||||
neutrino.WaitForMoreCFHeaders = time.Millisecond * 100
|
||||
neutrino.BanDuration = time.Millisecond * 100
|
||||
neutrino.QueryTimeout = time.Millisecond * 500
|
||||
neutrino.QueryNumRetries = 2
|
||||
// Start Alice - open a database, start a neutrino
|
||||
// instance, and initialize a btcwallet driver for it.
|
||||
aliceDB, err := walletdb.Create("bdb",
|
||||
tempTestDirAlice+"/neutrino.db")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
t.Fatalf("unable to create DB: %v", err)
|
||||
}
|
||||
defer aliceDB.Close()
|
||||
aliceChain, err := neutrino.NewChainService(
|
||||
neutrino.Config{
|
||||
DataDir: tempTestDirAlice,
|
||||
Database: aliceDB,
|
||||
Namespace: []byte("alice"),
|
||||
ChainParams: *netParams,
|
||||
ConnectPeers: []string{
|
||||
miningNode.P2PAddress(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make neutrino: %v", err)
|
||||
}
|
||||
aliceChain.Start()
|
||||
defer aliceChain.Stop()
|
||||
aliceClient = chain.NewNeutrinoClient(aliceChain)
|
||||
|
||||
// Start Bob - open a database, start a neutrino
|
||||
// instance, and initialize a btcwallet driver for it.
|
||||
bobDB, err := walletdb.Create("bdb",
|
||||
tempTestDirBob+"/neutrino.db")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create DB: %v", err)
|
||||
}
|
||||
defer bobDB.Close()
|
||||
bobChain, err := neutrino.NewChainService(
|
||||
neutrino.Config{
|
||||
DataDir: tempTestDirBob,
|
||||
Database: bobDB,
|
||||
Namespace: []byte("bob"),
|
||||
ChainParams: *netParams,
|
||||
ConnectPeers: []string{
|
||||
miningNode.P2PAddress(),
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to make neutrino: %v", err)
|
||||
}
|
||||
bobChain.Start()
|
||||
defer bobChain.Stop()
|
||||
bobClient = chain.NewNeutrinoClient(bobChain)
|
||||
case "bitcoind":
|
||||
feeEstimator, err = lnwallet.NewBitcoindFeeEstimator(
|
||||
rpcConfig, 250)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bitcoind fee estimator: %v",
|
||||
err)
|
||||
}
|
||||
// Start a bitcoind instance.
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp directory: %v", err)
|
||||
}
|
||||
zmqPath := "ipc:///" + tempBitcoindDir + "/weks.socket"
|
||||
defer os.RemoveAll(tempBitcoindDir)
|
||||
rpcPort := rand.Int()%(65536-1024) + 1024
|
||||
bitcoind := exec.Command(
|
||||
"bitcoind",
|
||||
"-datadir="+tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-connect="+miningNode.P2PAddress(),
|
||||
"-txindex",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6f"+
|
||||
"d$507c670e800a95284294edb5773b05544b"+
|
||||
"220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock="+zmqPath,
|
||||
"-zmqpubrawtx="+zmqPath,
|
||||
)
|
||||
err = bitcoind.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start bitcoind: %v", err)
|
||||
}
|
||||
defer bitcoind.Wait()
|
||||
defer bitcoind.Process.Kill()
|
||||
|
||||
// Start an Alice btcwallet bitcoind back end instance.
|
||||
aliceClient, err = chain.NewBitcoindClient(netParams,
|
||||
fmt.Sprintf("127.0.0.1:%d", rpcPort), "weks",
|
||||
"weks", zmqPath, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start alice client: %v", err)
|
||||
}
|
||||
|
||||
// Start a Bob btcwallet bitcoind back end instance.
|
||||
bobClient, err = chain.NewBitcoindClient(netParams,
|
||||
fmt.Sprintf("127.0.0.1:%d", rpcPort), "weks",
|
||||
"weks", zmqPath, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't start bob client: %v", err)
|
||||
}
|
||||
bobSigner = bobWalletController.(*btcwallet.BtcWallet)
|
||||
bio = bobWalletController.(*btcwallet.BtcWallet)
|
||||
default:
|
||||
// TODO(roasbeef): add neutrino case
|
||||
t.Fatalf("unknown wallet driver: %v", walletType)
|
||||
t.Fatalf("unknown chain driver: %v", backEnd)
|
||||
}
|
||||
|
||||
// Funding via 20 outputs with 4BTC each.
|
||||
alice, err := createTestWallet(tempTestDirAlice, miningNode,
|
||||
netParams, chainNotifier, aliceWalletController,
|
||||
aliceSigner, bio)
|
||||
aliceWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("alice-pass"),
|
||||
HdSeed: aliceHDSeed[:],
|
||||
DataDir: tempTestDirAlice,
|
||||
NetParams: netParams,
|
||||
ChainSource: aliceClient,
|
||||
FeeEstimator: feeEstimator,
|
||||
}
|
||||
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
defer alice.Shutdown()
|
||||
aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)
|
||||
|
||||
bob, err := createTestWallet(tempTestDirBob, miningNode,
|
||||
netParams, chainNotifier, bobWalletController,
|
||||
bobSigner, bio)
|
||||
bobWalletConfig := &btcwallet.Config{
|
||||
PrivatePass: []byte("bob-pass"),
|
||||
HdSeed: bobHDSeed[:],
|
||||
DataDir: tempTestDirBob,
|
||||
NetParams: netParams,
|
||||
ChainSource: bobClient,
|
||||
FeeEstimator: feeEstimator,
|
||||
}
|
||||
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
t.Fatalf("unable to create btcwallet: %v", err)
|
||||
}
|
||||
defer bob.Shutdown()
|
||||
bobSigner = bobWalletController.(*btcwallet.BtcWallet)
|
||||
bio = bobWalletController.(*btcwallet.BtcWallet)
|
||||
default:
|
||||
t.Fatalf("unknown wallet driver: %v", walletType)
|
||||
}
|
||||
|
||||
// Both wallets should now have 80BTC available for spending.
|
||||
assertProperBalance(t, alice, 1, 80)
|
||||
assertProperBalance(t, bob, 1, 80)
|
||||
// Funding via 20 outputs with 4BTC each.
|
||||
alice, err := createTestWallet(tempTestDirAlice, miningNode, netParams,
|
||||
chainNotifier, aliceWalletController, aliceSigner, bio)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
}
|
||||
defer alice.Shutdown()
|
||||
|
||||
// Execute every test, clearing possibly mutated wallet state
|
||||
// after each step.
|
||||
for _, walletTest := range walletTests {
|
||||
testName := fmt.Sprintf("%v:%v", walletType,
|
||||
walletTest.name)
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
walletTest.test(miningNode, alice, bob, t)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
bob, err := createTestWallet(tempTestDirBob, miningNode, netParams,
|
||||
chainNotifier, bobWalletController, bobSigner, bio)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test ln wallet: %v", err)
|
||||
}
|
||||
defer bob.Shutdown()
|
||||
|
||||
// TODO(roasbeef): possible reset mining node's
|
||||
// chainstate to initial level, cleanly wipe buckets
|
||||
if err := clearWalletStates(alice, bob); err != nil &&
|
||||
err != bolt.ErrBucketNotFound {
|
||||
t.Fatalf("unable to wipe wallet state: %v", err)
|
||||
}
|
||||
// Both wallets should now have 80BTC available for
|
||||
// spending.
|
||||
assertProperBalance(t, alice, 1, 80)
|
||||
assertProperBalance(t, bob, 1, 80)
|
||||
|
||||
// Execute every test, clearing possibly mutated
|
||||
// wallet state after each step.
|
||||
for _, walletTest := range walletTests {
|
||||
testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd,
|
||||
walletTest.name)
|
||||
success := t.Run(testName, func(t *testing.T) {
|
||||
walletTest.test(miningNode, alice, bob, t)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
|
||||
// TODO(roasbeef): possible reset mining
|
||||
// node's chainstate to initial level, cleanly
|
||||
// wipe buckets
|
||||
if err := clearWalletStates(alice, bob); err !=
|
||||
nil && err != bolt.ErrBucketNotFound {
|
||||
t.Fatalf("unable to wipe wallet state: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user