diff --git a/qa/rpc-tests/walletbackup.py b/qa/rpc-tests/walletbackup.py new file mode 100755 index 000000000..6a42d9dfa --- /dev/null +++ b/qa/rpc-tests/walletbackup.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014 The Bitcoin Core developers +# Distributed under the MIT/X11 software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Exercise the wallet backup code. Ported from walletbackup.sh. + +Test case is: +4 nodes. 1 2 and 3 send transactions between each other, +fourth node is a miner. +1 2 3 each mine a block to start, then +Miner creates 100 blocks so 1 2 3 each have 50 mature +coins to spend. +Then 5 iterations of 1/2/3 sending coins amongst +themselves to get transactions in the wallets, +and the miner mining one block. + +Wallets are backed up using dumpwallet/backupwallet. +Then 5 more iterations of transactions and mining a block. + +Miner then generates 101 more blocks, so any +transaction fees paid mature. + +Sanity check: + Sum(1,2,3,4 balances) == 114*50 + +1/2/3 are shutdown, and their wallets erased. +Then restore using wallet.dat backup. And +confirm 1/2/3/4 balances are same as before. + +Shutdown again, restore using importwallet, +and confirm again balances are correct. +""" + +from test_framework import BitcoinTestFramework +from util import * +from random import randint +import logging +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + +class WalletBackupTest(BitcoinTestFramework): + + def setup_chain(self): + logging.info("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + # This mirrors how the network was setup in the bash test + def setup_network(self, split=False): + # nodes 1, 2,3 are spenders, let's give them a keypool=100 + extra_args = [["-keypool=100"], ["-keypool=100"], ["-keypool=100"], []] + self.nodes = start_nodes(4, self.options.tmpdir, extra_args) + connect_nodes(self.nodes[0], 3) + connect_nodes(self.nodes[1], 3) + connect_nodes(self.nodes[2], 3) + connect_nodes(self.nodes[2], 0) + self.is_network_split=False + self.sync_all() + + def one_send(self, from_node, to_address): + if (randint(1,2) == 1): + amount = Decimal(randint(1,10)) / Decimal(10) + self.nodes[from_node].sendtoaddress(to_address, amount) + + def do_one_round(self): + a0 = self.nodes[0].getnewaddress() + a1 = self.nodes[1].getnewaddress() + a2 = self.nodes[2].getnewaddress() + + self.one_send(0, a1) + self.one_send(0, a2) + self.one_send(1, a0) + self.one_send(1, a2) + self.one_send(2, a0) + self.one_send(2, a1) + + # Have the miner (node3) mine a block. + # Must sync mempools before mining. + sync_mempools(self.nodes) + self.nodes[3].setgenerate(True, 1) + + # As above, this mirrors the original bash test. + def start_three(self): + self.nodes[0] = start_node(0, self.options.tmpdir) + self.nodes[1] = start_node(1, self.options.tmpdir) + self.nodes[2] = start_node(2, self.options.tmpdir) + connect_nodes(self.nodes[0], 3) + connect_nodes(self.nodes[1], 3) + connect_nodes(self.nodes[2], 3) + connect_nodes(self.nodes[2], 0) + + def stop_three(self): + stop_node(self.nodes[0], 0) + stop_node(self.nodes[1], 1) + stop_node(self.nodes[2], 2) + + def erase_three(self): + os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat") + os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat") + os.remove(self.options.tmpdir + "/node2/regtest/wallet.dat") + + def run_test(self): + logging.info("Generating initial blockchain") + self.nodes[0].setgenerate(True, 1) + sync_blocks(self.nodes) + self.nodes[1].setgenerate(True, 1) + sync_blocks(self.nodes) + self.nodes[2].setgenerate(True, 1) + sync_blocks(self.nodes) + self.nodes[3].setgenerate(True, 100) + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getbalance(), 50) + assert_equal(self.nodes[1].getbalance(), 50) + assert_equal(self.nodes[2].getbalance(), 50) + assert_equal(self.nodes[3].getbalance(), 0) + + logging.info("Creating transactions") + # Five rounds of sending each other transactions. + for i in range(5): + self.do_one_round() + + logging.info("Backing up") + tmpdir = self.options.tmpdir + self.nodes[0].backupwallet(tmpdir + "/node0/wallet.bak") + self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.dump") + self.nodes[1].backupwallet(tmpdir + "/node1/wallet.bak") + self.nodes[1].dumpwallet(tmpdir + "/node1/wallet.dump") + self.nodes[2].backupwallet(tmpdir + "/node2/wallet.bak") + self.nodes[2].dumpwallet(tmpdir + "/node2/wallet.dump") + + logging.info("More transactions") + for i in range(5): + self.do_one_round() + + # Generate 101 more blocks, so any fees paid mature + self.nodes[3].setgenerate(True, 101) + self.sync_all() + + balance0 = self.nodes[0].getbalance() + balance1 = self.nodes[1].getbalance() + balance2 = self.nodes[2].getbalance() + balance3 = self.nodes[3].getbalance() + total = balance0 + balance1 + balance2 + balance3 + + # At this point, there are 214 blocks (103 for setup, then 10 rounds, then 101.) + # 114 are mature, so the sum of all wallets should be 114 * 50 = 5700. + assert_equal(total, 5700) + + ## + # Test restoring spender wallets from backups + ## + logging.info("Restoring using wallet.dat") + self.stop_three() + self.erase_three() + + # Start node2 with no chain + shutil.rmtree(self.options.tmpdir + "/node2/regtest/blocks") + shutil.rmtree(self.options.tmpdir + "/node2/regtest/chainstate") + + # Restore wallets from backup + shutil.copyfile(tmpdir + "/node0/wallet.bak", tmpdir + "/node0/regtest/wallet.dat") + shutil.copyfile(tmpdir + "/node1/wallet.bak", tmpdir + "/node1/regtest/wallet.dat") + shutil.copyfile(tmpdir + "/node2/wallet.bak", tmpdir + "/node2/regtest/wallet.dat") + + logging.info("Re-starting nodes") + self.start_three() + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getbalance(), balance0) + assert_equal(self.nodes[1].getbalance(), balance1) + assert_equal(self.nodes[2].getbalance(), balance2) + + logging.info("Restoring using dumped wallet") + self.stop_three() + self.erase_three() + + #start node2 with no chain + shutil.rmtree(self.options.tmpdir + "/node2/regtest/blocks") + shutil.rmtree(self.options.tmpdir + "/node2/regtest/chainstate") + + self.start_three() + + assert_equal(self.nodes[0].getbalance(), 0) + assert_equal(self.nodes[1].getbalance(), 0) + assert_equal(self.nodes[2].getbalance(), 0) + + self.nodes[0].importwallet(tmpdir + "/node0/wallet.dump") + self.nodes[1].importwallet(tmpdir + "/node1/wallet.dump") + self.nodes[2].importwallet(tmpdir + "/node2/wallet.dump") + + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getbalance(), balance0) + assert_equal(self.nodes[1].getbalance(), balance1) + assert_equal(self.nodes[2].getbalance(), balance2) + + +if __name__ == '__main__': + WalletBackupTest().main() diff --git a/qa/rpc-tests/walletbackup.sh b/qa/rpc-tests/walletbackup.sh deleted file mode 100755 index 4af3d97f3..000000000 --- a/qa/rpc-tests/walletbackup.sh +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2014 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -# Test wallet backup / dump / restore functionality - -# Test case is: -# 4 nodes. 1 2 3 and send transactions between each other, -# fourth node is a miner. -# 1 2 3 and each mine a block to start, then -# miner creates 100 blocks so 1 2 3 each have 50 mature -# coins to spend. -# Then 5 iterations of 1/2/3 sending coins amongst -# themselves to get transactions in the wallets, -# and the miner mining one block. -# -# Wallets are backed up using dumpwallet/backupwallet. -# Then 5 more iterations of transactions, then block. -# -# Miner then generates 101 more blocks, so any -# transaction fees paid mature. -# -# Sanity checks done: -# Miner balance >= 150*50 -# Sum(1,2,3,4 balances) == 153*150 -# -# 1/2/3 are shutdown, and their wallets erased. -# Then restore using wallet.dat backup. And -# confirm 1/2/3/4 balances are same as before. -# -# Shutdown again, restore using importwallet, -# and confirm again balances are correct. -# - -if [ $# -lt 1 ]; then - echo "Usage: $0 path_to_binaries" - echo "e.g. $0 ../../src" - echo "Env vars BITCOIND and BITCOINCLI may be used to specify the exact binaries used" - exit 1 -fi - -BITCOIND=${BITCOIND:-${1}/bitcoind} -CLI=${BITCOINCLI:-${1}/bitcoin-cli} - -DIR="${BASH_SOURCE%/*}" -SENDANDWAIT="${DIR}/send.sh" -if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi -. "$DIR/util.sh" - -D=$(mktemp -d test.XXXXX) - -echo "Starting nodes..." - -# "Miner": -D4=${D}/node4 -CreateDataDir $D4 port=11030 rpcport=11031 -B4ARGS="-datadir=$D4" -$BITCOIND $BITCOINDARGS $B4ARGS & -B4PID=$! - -# Want default keypool for 1/2/3, and -# don't need send-and-wait functionality, -# so don't use CreateDataDir: -function CreateConfDir { - DIR=$1 - mkdir -p $DIR - CONF=$DIR/bitcoin.conf - echo "regtest=1" >> $CONF - echo "rpcuser=rt" >> $CONF - echo "rpcpassword=rt" >> $CONF - echo "rpcwait=1" >> $CONF - shift - while (( "$#" )); do - echo $1 >> $CONF - shift - done -} - -# "Spenders" 1/2/3 -D1=${D}/node1 -CreateConfDir $D1 port=11000 rpcport=11001 addnode=127.0.0.1:11030 -B1ARGS="-datadir=$D1" -$BITCOIND $B1ARGS & -B1PID=$! -D2=${D}/node2 -CreateConfDir $D2 port=11010 rpcport=11011 addnode=127.0.0.1:11030 -B2ARGS="-datadir=$D2" -$BITCOIND $B2ARGS & -B2PID=$! -D3=${D}/node3 -CreateConfDir $D3 port=11020 rpcport=11021 addnode=127.0.0.1:11030 addnode=127.0.0.1:11000 -B3ARGS="-datadir=$D3" -$BITCOIND $BITCOINDARGS $B3ARGS & -B3PID=$! - -# Wait until all nodes are at the same block number -function WaitBlocks { - while : - do - sleep 1 - BLOCKS1=$( GetBlocks "$B1ARGS" ) - BLOCKS2=$( GetBlocks "$B2ARGS" ) - BLOCKS3=$( GetBlocks "$B3ARGS" ) - BLOCKS4=$( GetBlocks "$B4ARGS" ) - if (( BLOCKS1 == BLOCKS4 && BLOCKS2 == BLOCKS4 && BLOCKS3 == BLOCKS4 )) - then - break - fi - done -} - -# Wait until all nodes have the same txns in -# their memory pools -function WaitMemPools { - while : - do - sleep 1 - MEMPOOL1=$( $CLI "$B1ARGS" getrawmempool | sort | shasum ) - MEMPOOL2=$( $CLI "$B2ARGS" getrawmempool | sort | shasum ) - MEMPOOL3=$( $CLI "$B3ARGS" getrawmempool | sort | shasum ) - MEMPOOL4=$( $CLI "$B4ARGS" getrawmempool | sort | shasum ) - if [[ $MEMPOOL1 = $MEMPOOL4 && $MEMPOOL2 = $MEMPOOL4 && $MEMPOOL3 = $MEMPOOL4 ]] - then - break - fi - done -} - -echo "Generating initial blockchain..." - -# 1 block, 50 XBT each == 50 BTC -$CLI $B1ARGS setgenerate true 1 -WaitBlocks -$CLI $B2ARGS setgenerate true 1 -WaitBlocks -$CLI $B3ARGS setgenerate true 1 -WaitBlocks - -# 100 blocks, 0 mature -$CLI $B4ARGS setgenerate true 100 -WaitBlocks - -CheckBalance "$B1ARGS" 50 -CheckBalance "$B2ARGS" 50 -CheckBalance "$B3ARGS" 50 -CheckBalance "$B4ARGS" 0 - -echo "Creating transactions..." - -function S { - TXID=$( $CLI -datadir=${D}/node${1} sendtoaddress ${2} "${3}" 0 ) - if [ x$TXID = x ] ; then - echoerr "node${1}: error sending ${3} btc" - echo -n "node${1} balance: " - $CLI -datadir=${D}/node${1} getbalance "*" 0 - exit 1 - fi -} - -function OneRound { - A1=$( $CLI $B1ARGS getnewaddress ) - A2=$( $CLI $B2ARGS getnewaddress ) - A3=$( $CLI $B3ARGS getnewaddress ) - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 1 $A2 "0.$N" - fi - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 1 $A3 "0.0$N" - fi - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 2 $A1 "0.$N" - fi - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 2 $A3 "0.$N" - fi - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 3 $A1 "0.$N" - fi - if [[ $(( $RANDOM%2 )) < 1 ]] ; then - N=$(( $RANDOM % 9 + 1 )) - S 3 $A2 "0.0$N" - fi - $CLI "$B4ARGS" setgenerate true 1 -} - -for i in {1..5}; do OneRound ; done - -echo "Backing up..." - -$CLI "$B1ARGS" backupwallet "$D1/wallet.bak" -$CLI "$B1ARGS" dumpwallet "$D1/wallet.dump" -$CLI "$B2ARGS" backupwallet "$D2/wallet.bak" -$CLI "$B2ARGS" dumpwallet "$D2/wallet.dump" -$CLI "$B3ARGS" backupwallet "$D3/wallet.bak" -$CLI "$B3ARGS" dumpwallet "$D3/wallet.dump" - -echo "More transactions..." -for i in {1..5}; do OneRound ; done - -WaitMemPools - -# Generate 101 more blocks, so any fees paid -# mature -$CLI "$B4ARGS" setgenerate true 101 - -BALANCE1=$( $CLI "$B1ARGS" getbalance ) -BALANCE2=$( $CLI "$B2ARGS" getbalance ) -BALANCE3=$( $CLI "$B3ARGS" getbalance ) -BALANCE4=$( $CLI "$B4ARGS" getbalance ) - -TOTAL=$( dc -e "$BALANCE1 $BALANCE2 $BALANCE3 $BALANCE4 + + + p" ) - -AssertEqual $TOTAL 5700.00000000 - -function StopThree { - $CLI $B1ARGS stop > /dev/null 2>&1 - $CLI $B2ARGS stop > /dev/null 2>&1 - $CLI $B3ARGS stop > /dev/null 2>&1 - wait $B1PID - wait $B2PID - wait $B3PID -} -function EraseThree { - rm $D1/regtest/wallet.dat - rm $D2/regtest/wallet.dat - rm $D3/regtest/wallet.dat -} -function StartThree { - $BITCOIND $BITCOINDARGS $B1ARGS & - B1PID=$! - $BITCOIND $BITCOINDARGS $B2ARGS & - B2PID=$! - $BITCOIND $BITCOINDARGS $B3ARGS & - B3PID=$! -} - -echo "Restoring using wallet.dat" - -StopThree -EraseThree - -# Start node3 with no chain -rm -rf $D3/regtest/blocks -rm -rf $D3/regtest/chainstate -rm -rf $D3/regtest/database - -cp $D1/wallet.bak $D1/regtest/wallet.dat -cp $D2/wallet.bak $D2/regtest/wallet.dat -cp $D3/wallet.bak $D3/regtest/wallet.dat - -StartThree -WaitBlocks - -AssertEqual $BALANCE1 $( $CLI "$B1ARGS" getbalance ) -AssertEqual $BALANCE2 $( $CLI "$B2ARGS" getbalance ) -AssertEqual $BALANCE3 $( $CLI "$B3ARGS" getbalance ) - -echo "Restoring using dumped wallet" - -StopThree -EraseThree - -# Start node3 with no chain -rm -rf $D3/regtest/blocks -rm -rf $D3/regtest/chainstate -rm -rf $D3/regtest/database - -StartThree - -AssertEqual 0 $( $CLI "$B1ARGS" getbalance ) -AssertEqual 0 $( $CLI "$B2ARGS" getbalance ) -AssertEqual 0 $( $CLI "$B3ARGS" getbalance ) - -$CLI "$B1ARGS" importwallet $D1/wallet.dump -$CLI "$B2ARGS" importwallet $D2/wallet.dump -$CLI "$B3ARGS" importwallet $D3/wallet.dump - -WaitBlocks - -AssertEqual $BALANCE1 $( $CLI "$B1ARGS" getbalance ) -AssertEqual $BALANCE2 $( $CLI "$B2ARGS" getbalance ) -AssertEqual $BALANCE3 $( $CLI "$B3ARGS" getbalance ) - -StopThree -$CLI $B4ARGS stop > /dev/null 2>&1 -wait $B4PID - -echo "Tests successful, cleaning up" -trap "" EXIT -rm -rf $D -exit 0