You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
8.6 KiB
316 lines
8.6 KiB
// +build dev |
|
|
|
package chainntnfs |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"io/ioutil" |
|
"math/rand" |
|
"os" |
|
"os/exec" |
|
"path/filepath" |
|
"testing" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/btcjson" |
|
"github.com/btcsuite/btcd/chaincfg" |
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/integration/rpctest" |
|
"github.com/btcsuite/btcd/txscript" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/btcsuite/btcwallet/chain" |
|
"github.com/btcsuite/btcwallet/walletdb" |
|
"github.com/lightninglabs/neutrino" |
|
"github.com/lightningnetwork/lnd/kvdb" |
|
) |
|
|
|
var ( |
|
// TrickleInterval is the interval at which the miner should trickle |
|
// transactions to its peers. We'll set it small to ensure the miner |
|
// propagates transactions quickly in the tests. |
|
TrickleInterval = 10 * time.Millisecond |
|
) |
|
|
|
var ( |
|
NetParams = &chaincfg.RegressionNetParams |
|
) |
|
|
|
// randPubKeyHashScript generates a P2PKH script that pays to the public key of |
|
// a randomly-generated private key. |
|
func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) { |
|
privKey, err := btcec.NewPrivateKey(btcec.S256()) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed()) |
|
addrScript, err := btcutil.NewAddressPubKeyHash(pubKeyHash, NetParams) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
pkScript, err := txscript.PayToAddrScript(addrScript) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
return pkScript, privKey, nil |
|
} |
|
|
|
// GetTestTxidAndScript generate a new test transaction and returns its txid and |
|
// the script of the output being generated. |
|
func GetTestTxidAndScript(h *rpctest.Harness) (*chainhash.Hash, []byte, error) { |
|
pkScript, _, err := randPubKeyHashScript() |
|
if err != nil { |
|
return nil, nil, fmt.Errorf("unable to generate pkScript: %v", err) |
|
} |
|
output := &wire.TxOut{Value: 2e8, PkScript: pkScript} |
|
txid, err := h.SendOutputs([]*wire.TxOut{output}, 10) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
return txid, pkScript, nil |
|
} |
|
|
|
// WaitForMempoolTx waits for the txid to be seen in the miner's mempool. |
|
func WaitForMempoolTx(miner *rpctest.Harness, txid *chainhash.Hash) error { |
|
timeout := time.After(10 * time.Second) |
|
trickle := time.After(2 * TrickleInterval) |
|
for { |
|
// Check for the harness' knowledge of the txid. |
|
tx, err := miner.Client.GetRawTransaction(txid) |
|
if err != nil { |
|
jsonErr, ok := err.(*btcjson.RPCError) |
|
if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo { |
|
continue |
|
} |
|
return err |
|
} |
|
|
|
if tx != nil && tx.Hash().IsEqual(txid) { |
|
break |
|
} |
|
|
|
select { |
|
case <-time.After(100 * time.Millisecond): |
|
case <-timeout: |
|
return errors.New("timed out waiting for tx") |
|
} |
|
} |
|
|
|
// To ensure any transactions propagate from the miner to the peers |
|
// before returning, ensure we have waited for at least |
|
// 2*trickleInterval before returning. |
|
select { |
|
case <-trickle: |
|
case <-timeout: |
|
return errors.New("timeout waiting for trickle interval. " + |
|
"Trickle interval to large?") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// CreateSpendableOutput creates and returns an output that can be spent later |
|
// on. |
|
func CreateSpendableOutput(t *testing.T, |
|
miner *rpctest.Harness) (*wire.OutPoint, *wire.TxOut, *btcec.PrivateKey) { |
|
|
|
t.Helper() |
|
|
|
// Create a transaction that only has one output, the one destined for |
|
// the recipient. |
|
pkScript, privKey, err := randPubKeyHashScript() |
|
if err != nil { |
|
t.Fatalf("unable to generate pkScript: %v", err) |
|
} |
|
output := &wire.TxOut{Value: 2e8, PkScript: pkScript} |
|
txid, err := miner.SendOutputsWithoutChange([]*wire.TxOut{output}, 10) |
|
if err != nil { |
|
t.Fatalf("unable to create tx: %v", err) |
|
} |
|
|
|
// Mine the transaction to mark the output as spendable. |
|
if err := WaitForMempoolTx(miner, txid); err != nil { |
|
t.Fatalf("tx not relayed to miner: %v", err) |
|
} |
|
if _, err := miner.Client.Generate(1); err != nil { |
|
t.Fatalf("unable to generate single block: %v", err) |
|
} |
|
|
|
return wire.NewOutPoint(txid, 0), output, privKey |
|
} |
|
|
|
// CreateSpendTx creates a transaction spending the specified output. |
|
func CreateSpendTx(t *testing.T, prevOutPoint *wire.OutPoint, |
|
prevOutput *wire.TxOut, privKey *btcec.PrivateKey) *wire.MsgTx { |
|
|
|
t.Helper() |
|
|
|
spendingTx := wire.NewMsgTx(1) |
|
spendingTx.AddTxIn(&wire.TxIn{PreviousOutPoint: *prevOutPoint}) |
|
spendingTx.AddTxOut(&wire.TxOut{Value: 1e8, PkScript: prevOutput.PkScript}) |
|
|
|
sigScript, err := txscript.SignatureScript( |
|
spendingTx, 0, prevOutput.PkScript, txscript.SigHashAll, |
|
privKey, true, |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to sign tx: %v", err) |
|
} |
|
spendingTx.TxIn[0].SignatureScript = sigScript |
|
|
|
return spendingTx |
|
} |
|
|
|
// NewMiner spawns testing harness backed by a btcd node that can serve as a |
|
// miner. |
|
func NewMiner(t *testing.T, extraArgs []string, createChain bool, |
|
spendableOutputs uint32) (*rpctest.Harness, func()) { |
|
|
|
t.Helper() |
|
|
|
// Add the trickle interval argument to the extra args. |
|
trickle := fmt.Sprintf("--trickleinterval=%v", TrickleInterval) |
|
extraArgs = append(extraArgs, trickle) |
|
|
|
node, err := rpctest.New(NetParams, nil, extraArgs, "") |
|
if err != nil { |
|
t.Fatalf("unable to create backend node: %v", err) |
|
} |
|
if err := node.SetUp(createChain, spendableOutputs); err != nil { |
|
node.TearDown() |
|
t.Fatalf("unable to set up backend node: %v", err) |
|
} |
|
|
|
return node, func() { node.TearDown() } |
|
} |
|
|
|
// NewBitcoindBackend spawns a new bitcoind node that connects to a miner at the |
|
// specified address. The txindex boolean can be set to determine whether the |
|
// backend node should maintain a transaction index. A connection to the newly |
|
// spawned bitcoind node is returned. |
|
func NewBitcoindBackend(t *testing.T, minerAddr string, |
|
txindex bool) (*chain.BitcoindConn, func()) { |
|
|
|
t.Helper() |
|
|
|
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind") |
|
if err != nil { |
|
t.Fatalf("unable to create temp dir: %v", err) |
|
} |
|
|
|
rpcPort := rand.Intn(65536-1024) + 1024 |
|
zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket" |
|
zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket" |
|
|
|
args := []string{ |
|
"-connect=" + minerAddr, |
|
"-datadir=" + tempBitcoindDir, |
|
"-regtest", |
|
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a952" + |
|
"84294edb5773b05544b220110063096c221be9933c82d38e1", |
|
fmt.Sprintf("-rpcport=%d", rpcPort), |
|
"-disablewallet", |
|
"-zmqpubrawblock=" + zmqBlockHost, |
|
"-zmqpubrawtx=" + zmqTxHost, |
|
} |
|
if txindex { |
|
args = append(args, "-txindex") |
|
} |
|
|
|
bitcoind := exec.Command("bitcoind", args...) |
|
if err := bitcoind.Start(); err != nil { |
|
os.RemoveAll(tempBitcoindDir) |
|
t.Fatalf("unable to start bitcoind: %v", err) |
|
} |
|
|
|
// Wait for the bitcoind instance to start up. |
|
time.Sleep(time.Second) |
|
|
|
host := fmt.Sprintf("127.0.0.1:%d", rpcPort) |
|
conn, err := chain.NewBitcoindConn(&chain.BitcoindConfig{ |
|
ChainParams: NetParams, |
|
Host: host, |
|
User: "weks", |
|
Pass: "weks", |
|
ZMQBlockHost: zmqBlockHost, |
|
ZMQTxHost: zmqTxHost, |
|
ZMQReadDeadline: 5 * time.Second, |
|
// Fields only required for pruned nodes, not needed for these |
|
// tests. |
|
Dialer: nil, |
|
PrunedModeMaxPeers: 0, |
|
}) |
|
if err != nil { |
|
bitcoind.Process.Kill() |
|
bitcoind.Wait() |
|
os.RemoveAll(tempBitcoindDir) |
|
t.Fatalf("unable to establish connection to bitcoind: %v", err) |
|
} |
|
if err := conn.Start(); err != nil { |
|
bitcoind.Process.Kill() |
|
bitcoind.Wait() |
|
os.RemoveAll(tempBitcoindDir) |
|
t.Fatalf("unable to establish connection to bitcoind: %v", err) |
|
} |
|
|
|
return conn, func() { |
|
conn.Stop() |
|
bitcoind.Process.Kill() |
|
bitcoind.Wait() |
|
os.RemoveAll(tempBitcoindDir) |
|
} |
|
} |
|
|
|
// NewNeutrinoBackend spawns a new neutrino node that connects to a miner at |
|
// the specified address. |
|
func NewNeutrinoBackend(t *testing.T, minerAddr string) (*neutrino.ChainService, func()) { |
|
t.Helper() |
|
|
|
spvDir, err := ioutil.TempDir("", "neutrino") |
|
if err != nil { |
|
t.Fatalf("unable to create temp dir: %v", err) |
|
} |
|
|
|
dbName := filepath.Join(spvDir, "neutrino.db") |
|
spvDatabase, err := walletdb.Create( |
|
"bdb", dbName, true, kvdb.DefaultDBTimeout, |
|
) |
|
if err != nil { |
|
os.RemoveAll(spvDir) |
|
t.Fatalf("unable to create walletdb: %v", err) |
|
} |
|
|
|
// Create an instance of neutrino connected to the running btcd |
|
// instance. |
|
spvConfig := neutrino.Config{ |
|
DataDir: spvDir, |
|
Database: spvDatabase, |
|
ChainParams: *NetParams, |
|
ConnectPeers: []string{minerAddr}, |
|
} |
|
spvNode, err := neutrino.NewChainService(spvConfig) |
|
if err != nil { |
|
os.RemoveAll(spvDir) |
|
spvDatabase.Close() |
|
t.Fatalf("unable to create neutrino: %v", err) |
|
} |
|
|
|
// We'll also wait for the instance to sync up fully to the chain |
|
// generated by the btcd instance. |
|
spvNode.Start() |
|
for !spvNode.IsCurrent() { |
|
time.Sleep(time.Millisecond * 100) |
|
} |
|
|
|
return spvNode, func() { |
|
spvNode.Stop() |
|
spvDatabase.Close() |
|
os.RemoveAll(spvDir) |
|
} |
|
}
|
|
|