diff options
-rwxr-xr-x | qa/pull-tester/rpc-tests.sh | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/txn_doublespend.py | 120 | ||||
-rwxr-xr-x | qa/rpc-tests/txnmall.sh | 152 |
3 files changed, 122 insertions, 152 deletions
diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 0b5ad20642..cc24a67c08 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -18,6 +18,8 @@ fi if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then ${BUILDDIR}/qa/rpc-tests/wallet.sh "${BUILDDIR}/src" ${BUILDDIR}/qa/rpc-tests/listtransactions.py --srcdir "${BUILDDIR}/src" + ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --srcdir "${BUILDDIR}/src" + ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --mineblock --srcdir "${BUILDDIR}/src" #${BUILDDIR}/qa/rpc-tests/forknotify.py --srcdir "${BUILDDIR}/src" else echo "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled" diff --git a/qa/rpc-tests/txn_doublespend.py b/qa/rpc-tests/txn_doublespend.py new file mode 100755 index 0000000000..6125147ebc --- /dev/null +++ b/qa/rpc-tests/txn_doublespend.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +# 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 proper accounting with malleable transactions +# + +from test_framework import BitcoinTestFramework +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from decimal import Decimal +from util import * +import os +import shutil + +class TxnMallTest(BitcoinTestFramework): + + def add_options(self, parser): + parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true", + help="Test double-spend of 1-confirmed transaction") + + def setup_network(self): + # Start with split network: + return super(TxnMallTest, self).setup_network(True) + + def run_test(self): + # All nodes should start with 1,250 BTC: + starting_balance = 1250 + for i in range(4): + assert_equal(self.nodes[i].getbalance(), starting_balance) + self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress! + + # Assign coins to foo and bar accounts: + self.nodes[0].move("", "foo", 1220) + self.nodes[0].move("", "bar", 30) + assert_equal(self.nodes[0].getbalance(""), 0) + + # Coins are sent to node1_address + node1_address = self.nodes[1].getnewaddress("from0") + + # First: use raw transaction API to send 1210 BTC to node1_address, + # but don't broadcast: + (total_in, inputs) = gather_inputs(self.nodes[0], 1210) + change_address = self.nodes[0].getnewaddress("foo") + outputs = {} + outputs[change_address] = 40 + outputs[node1_address] = 1210 + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + doublespend = self.nodes[0].signrawtransaction(rawtx) + assert_equal(doublespend["complete"], True) + + # Create two transaction from node[0] to node[1]; the + # second must spend change from the first because the first + # spends all mature inputs: + txid1 = self.nodes[0].sendfrom("foo", node1_address, 1210, 0) + txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0) + + # Have node0 mine a block: + if (self.options.mine_block): + self.nodes[0].setgenerate(True, 1) + sync_blocks(self.nodes[0:2]) + + tx1 = self.nodes[0].gettransaction(txid1) + tx2 = self.nodes[0].gettransaction(txid2) + + # Node0's balance should be starting balance, plus 50BTC for another + # matured block, minus 1210, minus 20, and minus transaction fees: + expected = starting_balance + if self.options.mine_block: expected += 50 + expected += tx1["amount"] + tx1["fee"] + expected += tx2["amount"] + tx2["fee"] + assert_equal(self.nodes[0].getbalance(), expected) + + # foo and bar accounts should be debited: + assert_equal(self.nodes[0].getbalance("foo"), 1220+tx1["amount"]+tx1["fee"]) + assert_equal(self.nodes[0].getbalance("bar"), 30+tx2["amount"]+tx2["fee"]) + + if self.options.mine_block: + assert_equal(tx1["confirmations"], 1) + assert_equal(tx2["confirmations"], 1) + # Node1's "from0" balance should be both transaction amounts: + assert_equal(self.nodes[1].getbalance("from0"), -(tx1["amount"]+tx2["amount"])) + else: + assert_equal(tx1["confirmations"], 0) + assert_equal(tx2["confirmations"], 0) + + # Now give doublespend to miner: + mutated_txid = self.nodes[2].sendrawtransaction(doublespend["hex"]) + # ... mine a block... + self.nodes[2].setgenerate(True, 1) + + # Reconnect the split network, and sync chain: + connect_nodes(self.nodes[1], 2) + self.nodes[2].setgenerate(True, 1) # Mine another block to make sure we sync + sync_blocks(self.nodes) + + # Re-fetch transaction info: + tx1 = self.nodes[0].gettransaction(txid1) + tx2 = self.nodes[0].gettransaction(txid2) + + # Both transactions should be conflicted + assert_equal(tx1["confirmations"], -1) + assert_equal(tx2["confirmations"], -1) + + # Node0's total balance should be starting balance, plus 100BTC for + # two more matured blocks, minus 1210 for the double-spend: + expected = starting_balance + 100 - 1210 + assert_equal(self.nodes[0].getbalance(), expected) + assert_equal(self.nodes[0].getbalance("*"), expected) + + # foo account should be debited, but bar account should not: + assert_equal(self.nodes[0].getbalance("foo"), 1220-1210) + assert_equal(self.nodes[0].getbalance("bar"), 30) + + # Node1's "from" account balance should be just the mutated send: + assert_equal(self.nodes[1].getbalance("from0"), 1210) + +if __name__ == '__main__': + TxnMallTest().main() diff --git a/qa/rpc-tests/txnmall.sh b/qa/rpc-tests/txnmall.sh deleted file mode 100755 index 1296d54d92..0000000000 --- a/qa/rpc-tests/txnmall.sh +++ /dev/null @@ -1,152 +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 proper accounting with malleable transactions - -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 - -set -f - -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) - -# Two nodes; one will play the part of merchant, the -# other an evil transaction-mutating miner. - -D1=${D}/node1 -CreateDataDir $D1 port=11000 rpcport=11001 -B1ARGS="-datadir=$D1" -$BITCOIND $B1ARGS & -B1PID=$! - -D2=${D}/node2 -CreateDataDir $D2 port=11010 rpcport=11011 -B2ARGS="-datadir=$D2" -$BITCOIND $B2ARGS & -B2PID=$! - -# Wait until both nodes are at the same block number -function WaitBlocks { - while : - do - sleep 1 - declare -i BLOCKS1=$( GetBlocks $B1ARGS ) - declare -i BLOCKS2=$( GetBlocks $B2ARGS ) - if (( BLOCKS1 == BLOCKS2 )) - then - break - fi - done -} - -# Wait until node has $N peers -function WaitPeers { - while : - do - declare -i PEERS=$( $CLI $1 getconnectioncount ) - if (( PEERS == "$2" )) - then - break - fi - sleep 1 - done -} - -echo "Generating test blockchain..." - -# Start with B2 connected to B1: -$CLI $B2ARGS addnode 127.0.0.1:11000 onetry -WaitPeers "$B1ARGS" 1 - -# 1 block, 50 XBT each == 50 XBT -$CLI $B1ARGS setgenerate true 1 - -WaitBlocks -# 100 blocks, 0 mature == 0 XBT -$CLI $B2ARGS setgenerate true 100 -WaitBlocks - -CheckBalance "$B1ARGS" 50 -CheckBalance "$B2ARGS" 0 - -# restart B2 with no connection -$CLI $B2ARGS stop > /dev/null 2>&1 -wait $B2PID -$BITCOIND $B2ARGS & -B2PID=$! - -B2ADDRESS=$( $CLI $B2ARGS getaccountaddress "from1" ) - -# Have B1 create two transactions; second will -# spend change from first, since B1 starts with only a single -# 50 bitcoin output: -$CLI $B1ARGS move "" "foo" 10.0 > /dev/null -$CLI $B1ARGS move "" "bar" 10.0 > /dev/null -TXID1=$( $CLI $B1ARGS sendfrom foo $B2ADDRESS 1.0 0) -TXID2=$( $CLI $B1ARGS sendfrom bar $B2ADDRESS 2.0 0) - -# Mutate TXID1 and add it to B2's memory pool: -RAWTX1=$( $CLI $B1ARGS getrawtransaction $TXID1 ) -# RAWTX1 is hex-encoded, serialized transaction. So each -# byte is two characters; we'll prepend the first -# "push" in the scriptsig with OP_PUSHDATA1 (0x4c), -# and add one to the length of the signature. -# Fields are fixed; from the beginning: -# 4-byte version -# 1-byte varint number-of inputs (one in this case) -# 32-byte previous txid -# 4-byte previous output -# 1-byte varint length-of-scriptsig -# 1-byte PUSH this many bytes onto stack -# ... etc -# So: to mutate, we want to get byte 41 (hex characters 82-83), -# increment it, and insert 0x4c after it. -L=${RAWTX1:82:2} -NEWLEN=$( printf "%x" $(( 16#$L + 1 )) ) -MUTATEDTX1=${RAWTX1:0:82}${NEWLEN}4c${RAWTX1:84} -# ... give mutated tx1 to B2: -MUTATEDTXID=$( $CLI $B2ARGS sendrawtransaction $MUTATEDTX1 ) - -echo "TXID1: " $TXID1 -echo "Mutated: " $MUTATEDTXID - -# Re-connect nodes, and have B2 mine a block -# containing the mutant: -$CLI $B2ARGS addnode 127.0.0.1:11000 onetry -$CLI $B2ARGS setgenerate true 1 -WaitBlocks - -# B1 should have 49 BTC; the 2 BTC send is -# conflicted, and should not count in -# balances. -CheckBalance "$B1ARGS" 49 -CheckBalance "$B1ARGS" 49 "*" -CheckBalance "$B1ARGS" 9 "foo" -CheckBalance "$B1ARGS" 10 "bar" - -# B2 should have 51 BTC -CheckBalance "$B2ARGS" 51 -CheckBalance "$B2ARGS" 1 "from1" - -$CLI $B2ARGS stop > /dev/null 2>&1 -wait $B2PID -$CLI $B1ARGS stop > /dev/null 2>&1 -wait $B1PID - -echo "Tests successful, cleaning up" -rm -rf $D -exit 0 |