Port/fix txnmall.sh regression test
Ported txnmall.sh to Python, and updated to match recent transaction malleability changes. I also modified it so it tests both double-spending confirmed and unconfirmed (only-in-mempool) transactions. Renamed to txn_doublespend, since that is really what is being tested. And told the pull-tester to run both variations on this test.
This commit is contained in:
parent
b5d1b10929
commit
8656dbb095
@ -18,6 +18,8 @@ fi
|
|||||||
if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then
|
if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then
|
||||||
${BUILDDIR}/qa/rpc-tests/wallet.sh "${BUILDDIR}/src"
|
${BUILDDIR}/qa/rpc-tests/wallet.sh "${BUILDDIR}/src"
|
||||||
${BUILDDIR}/qa/rpc-tests/listtransactions.py --srcdir "${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"
|
#${BUILDDIR}/qa/rpc-tests/forknotify.py --srcdir "${BUILDDIR}/src"
|
||||||
else
|
else
|
||||||
echo "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled"
|
echo "No rpc tests to run. Wallet, utils, and bitcoind must all be enabled"
|
||||||
|
120
qa/rpc-tests/txn_doublespend.py
Executable file
120
qa/rpc-tests/txn_doublespend.py
Executable file
@ -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()
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user