aboutsummaryrefslogtreecommitdiff
path: root/qa/rpc-tests
diff options
context:
space:
mode:
Diffstat (limited to 'qa/rpc-tests')
-rw-r--r--qa/rpc-tests/README.md2
-rwxr-xr-xqa/rpc-tests/decodescript.py116
-rwxr-xr-xqa/rpc-tests/fundrawtransaction.py556
-rwxr-xr-xqa/rpc-tests/httpbasics.py34
-rwxr-xr-xqa/rpc-tests/nodehandling.py87
-rwxr-xr-xqa/rpc-tests/proxy_test.py62
-rwxr-xr-xqa/rpc-tests/txn_clone.py167
-rwxr-xr-xqa/rpc-tests/txn_doublespend.py79
8 files changed, 1044 insertions, 59 deletions
diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md
index 6221c93d8b..efc81e7a97 100644
--- a/qa/rpc-tests/README.md
+++ b/qa/rpc-tests/README.md
@@ -25,7 +25,7 @@ Run all possible tests with `qa/pull-tester/rpc-tests.sh -extended`.
Possible options:
-````
+```
-h, --help show this help message and exit
--nocleanup Leave bitcoinds and test.* datadir on exit or error
--noshutdown Don't stop bitcoinds after the test execution
diff --git a/qa/rpc-tests/decodescript.py b/qa/rpc-tests/decodescript.py
new file mode 100755
index 0000000000..ce3bc94ef7
--- /dev/null
+++ b/qa/rpc-tests/decodescript.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python2
+# Copyright (c) 2015 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+
+class DecodeScriptTest(BitcoinTestFramework):
+ """Tests decoding scripts via RPC command "decodescript"."""
+
+ def setup_chain(self):
+ print('Initializing test directory ' + self.options.tmpdir)
+ initialize_chain_clean(self.options.tmpdir, 1)
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(1, self.options.tmpdir)
+ self.is_network_split = False
+
+ def decodescript_script_sig(self):
+ signature = '304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001'
+ push_signature = '48' + signature
+ public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2'
+ push_public_key = '21' + public_key
+
+ # below are test cases for all of the standard transaction types
+
+ # 1) P2PK scriptSig
+ # the scriptSig of a public key scriptPubKey simply pushes a signature onto the stack
+ rpc_result = self.nodes[0].decodescript(push_signature)
+ assert_equal(signature, rpc_result['asm'])
+
+ # 2) P2PKH scriptSig
+ rpc_result = self.nodes[0].decodescript(push_signature + push_public_key)
+ assert_equal(signature + ' ' + public_key, rpc_result['asm'])
+
+ # 3) multisig scriptSig
+ # this also tests the leading portion of a P2SH multisig scriptSig
+ # OP_0 <A sig> <B sig>
+ rpc_result = self.nodes[0].decodescript('00' + push_signature + push_signature)
+ assert_equal('0 ' + signature + ' ' + signature, rpc_result['asm'])
+
+ # 4) P2SH scriptSig
+ # an empty P2SH redeemScript is valid and makes for a very simple test case.
+ # thus, such a spending scriptSig would just need to pass the outer redeemScript
+ # hash test and leave true on the top of the stack.
+ rpc_result = self.nodes[0].decodescript('5100')
+ assert_equal('1 0', rpc_result['asm'])
+
+ # 5) null data scriptSig - no such thing because null data scripts can not be spent.
+ # thus, no test case for that standard transaction type is here.
+
+ def decodescript_script_pub_key(self):
+ public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2'
+ push_public_key = '21' + public_key
+ public_key_hash = '11695b6cd891484c2d49ec5aa738ec2b2f897777'
+ push_public_key_hash = '14' + public_key_hash
+
+ # below are test cases for all of the standard transaction types
+
+ # 1) P2PK scriptPubKey
+ # <pubkey> OP_CHECKSIG
+ rpc_result = self.nodes[0].decodescript(push_public_key + 'ac')
+ assert_equal(public_key + ' OP_CHECKSIG', rpc_result['asm'])
+
+ # 2) P2PKH scriptPubKey
+ # OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
+ rpc_result = self.nodes[0].decodescript('76a9' + push_public_key_hash + '88ac')
+ assert_equal('OP_DUP OP_HASH160 ' + public_key_hash + ' OP_EQUALVERIFY OP_CHECKSIG', rpc_result['asm'])
+
+ # 3) multisig scriptPubKey
+ # <m> <A pubkey> <B pubkey> <C pubkey> <n> OP_CHECKMULTISIG
+ # just imagine that the pub keys used below are different.
+ # for our purposes here it does not matter that they are the same even though it is unrealistic.
+ rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_public_key + push_public_key + '53ae')
+ assert_equal('2 ' + public_key + ' ' + public_key + ' ' + public_key + ' 3 OP_CHECKMULTISIG', rpc_result['asm'])
+
+ # 4) P2SH scriptPubKey
+ # OP_HASH160 <Hash160(redeemScript)> OP_EQUAL.
+ # push_public_key_hash here should actually be the hash of a redeem script.
+ # but this works the same for purposes of this test.
+ rpc_result = self.nodes[0].decodescript('a9' + push_public_key_hash + '87')
+ assert_equal('OP_HASH160 ' + public_key_hash + ' OP_EQUAL', rpc_result['asm'])
+
+ # 5) null data scriptPubKey
+ # use a signature look-alike here to make sure that we do not decode random data as a signature.
+ # this matters if/when signature sighash decoding comes along.
+ # would want to make sure that no such decoding takes place in this case.
+ signature_imposter = '48304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001'
+ # OP_RETURN <data>
+ rpc_result = self.nodes[0].decodescript('6a' + signature_imposter)
+ assert_equal('OP_RETURN ' + signature_imposter[2:], rpc_result['asm'])
+
+ # 6) a CLTV redeem script. redeem scripts are in-effect scriptPubKey scripts, so adding a test here.
+ # OP_NOP2 is also known as OP_CHECKLOCKTIMEVERIFY.
+ # just imagine that the pub keys used below are different.
+ # for our purposes here it does not matter that they are the same even though it is unrealistic.
+ #
+ # OP_IF
+ # <receiver-pubkey> OP_CHECKSIGVERIFY
+ # OP_ELSE
+ # <lock-until> OP_NOP2 OP_DROP
+ # OP_ENDIF
+ # <sender-pubkey> OP_CHECKSIG
+ #
+ # lock until block 500,000
+ rpc_result = self.nodes[0].decodescript('63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac')
+ assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_NOP2 OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm'])
+
+ def run_test(self):
+ self.decodescript_script_sig()
+ self.decodescript_script_pub_key()
+
+if __name__ == '__main__':
+ DecodeScriptTest().main()
+
diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py
new file mode 100755
index 0000000000..e859b26433
--- /dev/null
+++ b/qa/rpc-tests/fundrawtransaction.py
@@ -0,0 +1,556 @@
+#!/usr/bin/env python2
+# 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.
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+from pprint import pprint
+from time import sleep
+
+# Create one-input, one-output, no-fee transaction:
+class RawTransactionsTest(BitcoinTestFramework):
+
+ def setup_chain(self):
+ print("Initializing test directory "+self.options.tmpdir)
+ initialize_chain_clean(self.options.tmpdir, 3)
+
+ def setup_network(self, split=False):
+ self.nodes = start_nodes(3, self.options.tmpdir)
+
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+
+ self.is_network_split=False
+ self.sync_all()
+
+ def run_test(self):
+ print "Mining blocks..."
+ feeTolerance = Decimal(0.00000002) #if the fee's positive delta is higher than this value tests will fail, neg. delta always fail the tests
+
+ self.nodes[2].generate(1)
+ self.nodes[0].generate(101)
+ self.sync_all()
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5);
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0);
+ self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ ###############
+ # simple test #
+ ###############
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(len(dec_tx['vin']), 1) #one vin coin
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+ assert_equal(fee + totalOut, 1.5) #the 1.5BTC coin must be taken
+
+ ##############################
+ # simple test with two coins #
+ ##############################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.2 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(len(dec_tx['vin']), 2) #one vin coin
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+ assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '')
+ assert_equal(fee + totalOut, 2.5) #the 1.5BTC+1.0BTC coins must have be taken
+
+ ##############################
+ # simple test with two coins #
+ ##############################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.6 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(len(dec_tx['vin']), 1) #one vin coin
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+ assert_equal(fee + totalOut, 5.0) #the 5.0BTC coin must have be taken
+
+
+ ################################
+ # simple test with two outputs #
+ ################################
+ inputs = [ ]
+ outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(len(dec_tx['vin']), 2) #one vin coin
+ assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '')
+ assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '')
+ assert_equal(fee + totalOut, 6.0) #the 5.0BTC + 1.0BTC coins must have be taken
+
+
+
+ #########################################################################
+ # test a fundrawtransaction with a VIN greater than the required amount #
+ #########################################################################
+ utx = False
+ listunspent = self.nodes[2].listunspent()
+ for aUtx in listunspent:
+ if aUtx['amount'] == 5.0:
+ utx = aUtx
+ break;
+
+ assert_equal(utx!=False, True)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
+
+
+
+ #####################################################################
+ # test a fundrawtransaction with which will not get a change output #
+ #####################################################################
+ utx = False
+ listunspent = self.nodes[2].listunspent()
+ for aUtx in listunspent:
+ if aUtx['amount'] == 5.0:
+ utx = aUtx
+ break;
+
+ assert_equal(utx!=False, True)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : Decimal(5.0) - fee - feeTolerance }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+
+ assert_equal(rawtxfund['changepos'], -1)
+ assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
+
+
+
+ #########################################################################
+ # test a fundrawtransaction with a VIN smaller than the required amount #
+ #########################################################################
+ utx = False
+ listunspent = self.nodes[2].listunspent()
+ for aUtx in listunspent:
+ if aUtx['amount'] == 1.0:
+ utx = aUtx
+ break;
+
+ assert_equal(utx!=False, True)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}]
+ outputs = { self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+
+ # 4-byte version + 1-byte vin count + 36-byte prevout then script_len
+ rawtx = rawtx[:82] + "0100" + rawtx[84:]
+
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+ assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for i, out in enumerate(dec_tx['vout']):
+ totalOut += out['value']
+ if outputs.has_key(out['scriptPubKey']['addresses'][0]):
+ matchingOuts+=1
+ else:
+ assert_equal(i, rawtxfund['changepos'])
+
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+ assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex'])
+
+ assert_equal(matchingOuts, 1)
+ assert_equal(len(dec_tx['vout']), 2)
+
+ assert_equal(fee + totalOut, 2.5) #this tx must use the 1.0BTC and the 1.5BTC coin
+
+
+ ###########################################
+ # test a fundrawtransaction with two VINs #
+ ###########################################
+ utx = False
+ utx2 = False
+ listunspent = self.nodes[2].listunspent()
+ for aUtx in listunspent:
+ if aUtx['amount'] == 1.0:
+ utx = aUtx
+ if aUtx['amount'] == 5.0:
+ utx2 = aUtx
+
+
+ assert_equal(utx!=False, True)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : 6.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+ if outputs.has_key(out['scriptPubKey']['addresses'][0]):
+ matchingOuts+=1
+
+ assert_equal(matchingOuts, 1)
+ assert_equal(len(dec_tx['vout']), 2)
+
+ matchingIns = 0
+ for vinOut in dec_tx['vin']:
+ for vinIn in inputs:
+ if vinIn['txid'] == vinOut['txid']:
+ matchingIns+=1
+
+ assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params
+ assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin
+
+
+ #########################################################
+ # test a fundrawtransaction with two VINs and two vOUTs #
+ #########################################################
+ utx = False
+ utx2 = False
+ listunspent = self.nodes[2].listunspent()
+ for aUtx in listunspent:
+ if aUtx['amount'] == 1.0:
+ utx = aUtx
+ if aUtx['amount'] == 5.0:
+ utx2 = aUtx
+
+
+ assert_equal(utx!=False, True)
+
+ inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ]
+ outputs = { self.nodes[0].getnewaddress() : 6.0, self.nodes[0].getnewaddress() : 1.0 }
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+ assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
+
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ fee = rawtxfund['fee']
+ dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
+ totalOut = 0
+ matchingOuts = 0
+ for out in dec_tx['vout']:
+ totalOut += out['value']
+ if outputs.has_key(out['scriptPubKey']['addresses'][0]):
+ matchingOuts+=1
+
+ assert_equal(matchingOuts, 2)
+ assert_equal(len(dec_tx['vout']), 3)
+ assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin
+
+
+ ##############################################
+ # test a fundrawtransaction with invalid vin #
+ ##############################################
+ listunspent = self.nodes[2].listunspent()
+ inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin!
+ outputs = { self.nodes[0].getnewaddress() : 1.0}
+ rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
+ dec_tx = self.nodes[2].decoderawtransaction(rawtx)
+
+ errorString = ""
+ try:
+ rawtxfund = self.nodes[2].fundrawtransaction(rawtx)
+ except JSONRPCException,e:
+ errorString = e.error['message']
+
+ assert_equal("Insufficient" in errorString, True);
+
+
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1);
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee);
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction with multiple outputs
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendmany("", outputs);
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee);
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ #compare fee of a 2of2 multisig p2sh transaction
+
+ # create 2of2 addr
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[1].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[1].validateaddress(addr2)
+
+ mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+
+ inputs = []
+ outputs = {mSigObj:1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.1);
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee);
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ #compare fee of a standard pubkeyhash transaction
+
+ # create 4of5 addr
+ addr1 = self.nodes[1].getnewaddress()
+ addr2 = self.nodes[1].getnewaddress()
+ addr3 = self.nodes[1].getnewaddress()
+ addr4 = self.nodes[1].getnewaddress()
+ addr5 = self.nodes[1].getnewaddress()
+
+ addr1Obj = self.nodes[1].validateaddress(addr1)
+ addr2Obj = self.nodes[1].validateaddress(addr2)
+ addr3Obj = self.nodes[1].validateaddress(addr3)
+ addr4Obj = self.nodes[1].validateaddress(addr4)
+ addr5Obj = self.nodes[1].validateaddress(addr5)
+
+ mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']])
+
+ inputs = []
+ outputs = {mSigObj:1.1}
+ rawTx = self.nodes[0].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[0].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.1);
+ signedFee = self.nodes[0].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee);
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance)
+ ############################################################
+
+
+ ############################################################
+ # spend a 2of2 multisig transaction over fundraw
+
+ # create 2of2 addr
+ addr1 = self.nodes[2].getnewaddress()
+ addr2 = self.nodes[2].getnewaddress()
+
+ addr1Obj = self.nodes[2].validateaddress(addr1)
+ addr2Obj = self.nodes[2].validateaddress(addr2)
+
+ mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
+
+
+ # send 1.2 BTC to msig addr
+ txId = self.nodes[0].sendtoaddress(mSigObj, 1.2);
+ self.sync_all()
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ oldBalance = self.nodes[1].getbalance()
+ inputs = []
+ outputs = {self.nodes[1].getnewaddress():1.1}
+ rawTx = self.nodes[2].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[2].fundrawtransaction(rawTx)
+
+ signedTx = self.nodes[2].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[2].sendrawtransaction(signedTx['hex'])
+ self.sync_all()
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ # make sure funds are received at node1
+ assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance())
+
+ ############################################################
+ # locked wallet test
+ self.nodes[1].encryptwallet("test")
+ self.nodes.pop(1)
+ stop_nodes(self.nodes)
+ wait_bitcoinds()
+
+ self.nodes = start_nodes(3, self.options.tmpdir)
+
+ connect_nodes_bi(self.nodes,0,1)
+ connect_nodes_bi(self.nodes,1,2)
+ connect_nodes_bi(self.nodes,0,2)
+ self.is_network_split=False
+ self.sync_all()
+
+ error = False
+ try:
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.2);
+ except:
+ error = True
+ assert(error)
+
+ oldBalance = self.nodes[0].getbalance()
+
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():1.1}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+
+ #now we need to unlock
+ self.nodes[1].walletpassphrase("test", 100)
+ signedTx = self.nodes[1].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[1].sendrawtransaction(signedTx['hex'])
+ self.sync_all()
+ self.nodes[1].generate(1)
+ self.sync_all()
+
+ # make sure funds are received at node1
+ assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance())
+
+
+
+ ###############################################
+ # multiple (~19) inputs tx test | Compare fee #
+ ###############################################
+
+ #empty node1, send some small coins from node0 to node1
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True);
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ for i in range(0,20):
+ self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01);
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #fund a tx with ~20 small inputs
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+
+ #create same transaction over sendtoaddress
+ txId = self.nodes[1].sendmany("", outputs);
+ signedFee = self.nodes[1].getrawmempool(True)[txId]['fee']
+
+ #compare fee
+ feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee);
+ assert(feeDelta >= 0 and feeDelta <= feeTolerance*19) #~19 inputs
+
+
+ #############################################
+ # multiple (~19) inputs tx test | sign/send #
+ #############################################
+
+ #again, empty node1, send some small coins from node0 to node1
+ self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True);
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ for i in range(0,20):
+ self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01);
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+
+ #fund a tx with ~20 small inputs
+ oldBalance = self.nodes[0].getbalance()
+
+ inputs = []
+ outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04}
+ rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
+ fundedTx = self.nodes[1].fundrawtransaction(rawTx)
+ fundedAndSignedTx = self.nodes[1].signrawtransaction(fundedTx['hex'])
+ txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex'])
+ self.sync_all()
+ self.nodes[0].generate(1)
+ self.sync_all()
+ assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
+
+
+if __name__ == '__main__':
+ RawTransactionsTest().main()
diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py
index 64ba49df64..8ccb821286 100755
--- a/qa/rpc-tests/httpbasics.py
+++ b/qa/rpc-tests/httpbasics.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
-# Test REST interface
+# Test rpc http basics
#
from test_framework.test_framework import BitcoinTestFramework
@@ -20,83 +20,83 @@ try:
except ImportError:
import urlparse
-class HTTPBasicsTest (BitcoinTestFramework):
+class HTTPBasicsTest (BitcoinTestFramework):
def setup_nodes(self):
return start_nodes(4, self.options.tmpdir, extra_args=[['-rpckeepalive=1'], ['-rpckeepalive=0'], [], []])
- def run_test(self):
-
+ def run_test(self):
+
#################################################
# lowlevel check for http persistent connection #
#################################################
url = urlparse.urlparse(self.nodes[0].url)
authpair = url.username + ':' + url.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
-
+
conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
-
+
#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out2 = conn.getresponse().read();
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
conn.close()
-
+
#same should be if we add keep-alive because this should be the std. behaviour
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection": "keep-alive"}
-
+
conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
-
+
#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out2 = conn.getresponse().read();
assert_equal('"error":null' in out1, True) #must also response with a correct json-rpc message
assert_equal(conn.sock!=None, True) #according to http/1.1 connection must still be open!
conn.close()
-
+
#now do the same with "Connection: close"
headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection":"close"}
-
+
conn = httplib.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
- assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
-
+ assert_equal(conn.sock!=None, False) #now the connection must be closed after the response
+
#node1 (2nd node) is running with disabled keep-alive option
urlNode1 = urlparse.urlparse(self.nodes[1].url)
authpair = urlNode1.username + ':' + urlNode1.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
-
+
conn = httplib.HTTPConnection(urlNode1.hostname, urlNode1.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false
-
+
#node2 (third node) is running with standard keep-alive parameters which means keep-alive is off
urlNode2 = urlparse.urlparse(self.nodes[2].url)
authpair = urlNode2.username + ':' + urlNode2.password
headers = {"Authorization": "Basic " + base64.b64encode(authpair)}
-
+
conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read();
assert_equal('"error":null' in out1, True)
assert_equal(conn.sock!=None, True) #connection must be closed because bitcoind should use keep-alive by default
-
+
if __name__ == '__main__':
HTTPBasicsTest ().main ()
diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py
new file mode 100755
index 0000000000..d89cfcf59b
--- /dev/null
+++ b/qa/rpc-tests/nodehandling.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python2
+# 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 node handling
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import *
+import base64
+
+try:
+ import http.client as httplib
+except ImportError:
+ import httplib
+try:
+ import urllib.parse as urlparse
+except ImportError:
+ import urlparse
+
+class NodeHandlingTest (BitcoinTestFramework):
+ def run_test(self):
+ ###########################
+ # setban/listbanned tests #
+ ###########################
+ assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point
+ self.nodes[2].setban("127.0.0.1", "add")
+ time.sleep(3) #wait till the nodes are disconected
+ assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ self.nodes[2].clearbanned()
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+ self.nodes[2].setban("127.0.0.0/24", "add")
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ try:
+ self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24
+ except:
+ pass
+ assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
+ try:
+ self.nodes[2].setban("127.0.0.1", "remove")
+ except:
+ pass
+ assert_equal(len(self.nodes[2].listbanned()), 1)
+ self.nodes[2].setban("127.0.0.0/24", "remove")
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+ self.nodes[2].clearbanned()
+ assert_equal(len(self.nodes[2].listbanned()), 0)
+
+ ##test persisted banlist
+ self.nodes[2].setban("127.0.0.0/32", "add")
+ self.nodes[2].setban("127.0.0.0/24", "add")
+ self.nodes[2].setban("192.168.0.1", "add", 1) #ban for 1 seconds
+ self.nodes[2].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) #ban for 1000 seconds
+ listBeforeShutdown = self.nodes[2].listbanned();
+ assert_equal("192.168.0.1/255.255.255.255", listBeforeShutdown[2]['address']) #must be here
+ time.sleep(2) #make 100% sure we expired 192.168.0.1 node time
+
+ #stop node
+ stop_node(self.nodes[2], 2)
+
+ self.nodes[2] = start_node(2, self.options.tmpdir)
+ listAfterShutdown = self.nodes[2].listbanned();
+ assert_equal("127.0.0.0/255.255.255.0", listAfterShutdown[0]['address'])
+ assert_equal("127.0.0.0/255.255.255.255", listAfterShutdown[1]['address'])
+ assert_equal("2001:4000::/ffff:e000:0:0:0:0:0:0", listAfterShutdown[2]['address'])
+
+ ###########################
+ # RPC disconnectnode test #
+ ###########################
+ url = urlparse.urlparse(self.nodes[1].url)
+ self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1)))
+ time.sleep(2) #disconnecting a node needs a little bit of time
+ for node in self.nodes[0].getpeerinfo():
+ assert(node['addr'] != url.hostname+":"+str(p2p_port(1)))
+
+ connect_nodes_bi(self.nodes,0,1) #reconnect the node
+ found = False
+ for node in self.nodes[0].getpeerinfo():
+ if node['addr'] == url.hostname+":"+str(p2p_port(1)):
+ found = True
+ assert(found)
+
+if __name__ == '__main__':
+ NodeHandlingTest ().main ()
diff --git a/qa/rpc-tests/proxy_test.py b/qa/rpc-tests/proxy_test.py
index 9a9b2f5300..3623c16162 100755
--- a/qa/rpc-tests/proxy_test.py
+++ b/qa/rpc-tests/proxy_test.py
@@ -68,10 +68,10 @@ class ProxyTest(BitcoinTestFramework):
['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'],
['-listen', '-debug=net', '-debug=proxy', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
- ['-listen', '-debug=net', '-debug=proxy', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0']
+ ['-listen', '-debug=net', '-debug=proxy', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
])
- def node_test(self, node, proxies, auth):
+ def node_test(self, node, proxies, auth, test_onion=True):
rv = []
# Test: outgoing IPv4 connection through node
node.addnode("15.61.23.23:1234", "onetry")
@@ -99,17 +99,18 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(cmd.password, None)
rv.append(cmd)
- # Test: outgoing onion connection through node
- node.addnode("bitcoinostk4e4re.onion:8333", "onetry")
- cmd = proxies[2].queue.get()
- assert(isinstance(cmd, Socks5Command))
- assert_equal(cmd.atyp, AddressType.DOMAINNAME)
- assert_equal(cmd.addr, "bitcoinostk4e4re.onion")
- assert_equal(cmd.port, 8333)
- if not auth:
- assert_equal(cmd.username, None)
- assert_equal(cmd.password, None)
- rv.append(cmd)
+ if test_onion:
+ # Test: outgoing onion connection through node
+ node.addnode("bitcoinostk4e4re.onion:8333", "onetry")
+ cmd = proxies[2].queue.get()
+ assert(isinstance(cmd, Socks5Command))
+ assert_equal(cmd.atyp, AddressType.DOMAINNAME)
+ assert_equal(cmd.addr, "bitcoinostk4e4re.onion")
+ assert_equal(cmd.port, 8333)
+ if not auth:
+ assert_equal(cmd.username, None)
+ assert_equal(cmd.password, None)
+ rv.append(cmd)
# Test: outgoing DNS name connection through node
node.addnode("node.noumenon:8333", "onetry")
@@ -139,8 +140,41 @@ class ProxyTest(BitcoinTestFramework):
assert_equal(len(credentials), 4)
# proxy on IPv6 localhost
- self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False)
+ self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False)
+
+ def networks_dict(d):
+ r = {}
+ for x in d['networks']:
+ r[x['name']] = x
+ return r
+
+ # test RPC getnetworkinfo
+ n0 = networks_dict(self.nodes[0].getnetworkinfo())
+ for net in ['ipv4','ipv6','onion']:
+ assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr))
+ assert_equal(n0[net]['proxy_randomize_credentials'], True)
+ assert_equal(n0['onion']['reachable'], True)
+
+ n1 = networks_dict(self.nodes[1].getnetworkinfo())
+ for net in ['ipv4','ipv6']:
+ assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
+ assert_equal(n1[net]['proxy_randomize_credentials'], False)
+ assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
+ assert_equal(n1['onion']['proxy_randomize_credentials'], False)
+ assert_equal(n1['onion']['reachable'], True)
+ n2 = networks_dict(self.nodes[2].getnetworkinfo())
+ for net in ['ipv4','ipv6','onion']:
+ assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr))
+ assert_equal(n2[net]['proxy_randomize_credentials'], True)
+ assert_equal(n2['onion']['reachable'], True)
+
+ n3 = networks_dict(self.nodes[3].getnetworkinfo())
+ for net in ['ipv4','ipv6']:
+ assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr))
+ assert_equal(n3[net]['proxy_randomize_credentials'], False)
+ assert_equal(n3['onion']['reachable'], False)
+
if __name__ == '__main__':
ProxyTest().main()
diff --git a/qa/rpc-tests/txn_clone.py b/qa/rpc-tests/txn_clone.py
new file mode 100755
index 0000000000..0d276ecc91
--- /dev/null
+++ b/qa/rpc-tests/txn_clone.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python2
+# 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 an equivalent malleability clone
+#
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.authproxy import AuthServiceProxy, JSONRPCException
+from decimal import Decimal
+from test_framework.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].settxfee(.001)
+
+ node0_address_foo = self.nodes[0].getnewaddress("foo")
+ fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
+ fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
+
+ node0_address_bar = self.nodes[0].getnewaddress("bar")
+ fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
+ fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
+
+ assert_equal(self.nodes[0].getbalance(""),
+ starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
+
+ # Coins are sent to node1_address
+ node1_address = self.nodes[1].getnewaddress("from0")
+
+ # Send tx1, and another transaction tx2 that won't be cloned
+ txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
+ txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
+
+ # Construct a clone of tx1, to be malleated
+ rawtx1 = self.nodes[0].getrawtransaction(txid1,1)
+ clone_inputs = [{"txid":rawtx1["vin"][0]["txid"],"vout":rawtx1["vin"][0]["vout"]}]
+ clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][0]["value"],
+ rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]:rawtx1["vout"][1]["value"]}
+ clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs)
+
+ # 3 hex manipulations on the clone are required
+
+ # manipulation 1. sequence is at version+#inputs+input+sigstub
+ posseq = 2*(4+1+36+1)
+ seqbe = '%08x' % rawtx1["vin"][0]["sequence"]
+ clone_raw = clone_raw[:posseq] + seqbe[6:8] + seqbe[4:6] + seqbe[2:4] + seqbe[0:2] + clone_raw[posseq + 8:]
+
+ # manipulation 2. createrawtransaction randomizes the order of its outputs, so swap them if necessary.
+ # output 0 is at version+#inputs+input+sigstub+sequence+#outputs
+ # 40 BTC serialized is 00286bee00000000
+ pos0 = 2*(4+1+36+1+4+1)
+ hex40 = "00286bee00000000"
+ output_len = 16 + 2 + 2 * int("0x" + clone_raw[pos0 + 16 : pos0 + 16 + 2], 0)
+ if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0 : pos0 + 16] != hex40 or
+ rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0 : pos0 + 16] == hex40):
+ output0 = clone_raw[pos0 : pos0 + output_len]
+ output1 = clone_raw[pos0 + output_len : pos0 + 2 * output_len]
+ clone_raw = clone_raw[:pos0] + output1 + output0 + clone_raw[pos0 + 2 * output_len:]
+
+ # manipulation 3. locktime is after outputs
+ poslt = pos0 + 2 * output_len
+ ltbe = '%08x' % rawtx1["locktime"]
+ clone_raw = clone_raw[:poslt] + ltbe[6:8] + ltbe[4:6] + ltbe[2:4] + ltbe[0:2] + clone_raw[poslt + 8:]
+
+ # Use a different signature hash type to sign. This creates an equivalent but malleated clone.
+ # Don't send the clone anywhere yet
+ tx1_clone = self.nodes[0].signrawtransaction(clone_raw, None, None, "ALL|ANYONECANPAY")
+ assert_equal(tx1_clone["complete"], True)
+
+ # Have node0 mine a block, if requested:
+ if (self.options.mine_block):
+ self.nodes[0].generate(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 tx1 and tx2 amounts, and minus transaction fees:
+ expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
+ 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", 0), 1219 + tx1["amount"] + tx1["fee"])
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29 + 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)
+
+ # Send clone and its parent to miner
+ self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
+ txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"])
+ # ... mine a block...
+ self.nodes[2].generate(1)
+
+ # Reconnect the split network, and sync chain:
+ connect_nodes(self.nodes[1], 2)
+ self.nodes[2].generate(1) # Mine another block to make sure we sync
+ sync_blocks(self.nodes)
+
+ # Re-fetch transaction info:
+ tx1 = self.nodes[0].gettransaction(txid1)
+ tx1_clone = self.nodes[0].gettransaction(txid1_clone)
+ tx2 = self.nodes[0].gettransaction(txid2)
+
+ # Verify expected confirmations
+ assert_equal(tx1["confirmations"], -1)
+ assert_equal(tx1_clone["confirmations"], 2)
+ assert_equal(tx2["confirmations"], 0)
+
+ # Check node0's total balance; should be same as before the clone, + 100 BTC for 2 matured,
+ # less possible orphaned matured subsidy
+ expected += 100
+ if (self.options.mine_block):
+ expected -= 50
+ assert_equal(self.nodes[0].getbalance(), expected)
+ assert_equal(self.nodes[0].getbalance("*", 0), expected)
+
+ # Check node0's individual account balances.
+ # "foo" should have been debited by the equivalent clone of tx1
+ assert_equal(self.nodes[0].getbalance("foo"), 1219 + tx1["amount"] + tx1["fee"])
+ # "bar" should have been debited by (possibly unconfirmed) tx2
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29 + tx2["amount"] + tx2["fee"])
+ # "" should have starting balance, less funding txes, plus subsidies
+ assert_equal(self.nodes[0].getbalance("", 0), starting_balance
+ - 1219
+ + fund_foo_tx["fee"]
+ - 29
+ + fund_bar_tx["fee"]
+ + 100)
+
+ # Node1's "from0" account balance
+ assert_equal(self.nodes[1].getbalance("from0", 0), -(tx1["amount"] + tx2["amount"]))
+
+if __name__ == '__main__':
+ TxnMallTest().main()
+
diff --git a/qa/rpc-tests/txn_doublespend.py b/qa/rpc-tests/txn_doublespend.py
index 99dcdae552..36081127b4 100755
--- a/qa/rpc-tests/txn_doublespend.py
+++ b/qa/rpc-tests/txn_doublespend.py
@@ -4,7 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
-# Test proper accounting with malleable transactions
+# Test proper accounting with a double-spend conflict
#
from test_framework.test_framework import BitcoinTestFramework
@@ -31,28 +31,40 @@ class TxnMallTest(BitcoinTestFramework):
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)
+ node0_address_foo = self.nodes[0].getnewaddress("foo")
+ fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
+ fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
+
+ node0_address_bar = self.nodes[0].getnewaddress("bar")
+ fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
+ fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
+
+ assert_equal(self.nodes[0].getbalance(""),
+ starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
# 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,
+ # First: use raw transaction API to send 1240 BTC to node1_address,
# but don't broadcast:
- (total_in, inputs) = gather_inputs(self.nodes[0], 1210)
- change_address = self.nodes[0].getnewaddress("foo")
+ doublespend_fee = Decimal('-.02')
+ rawtx_input_0 = {}
+ rawtx_input_0["txid"] = fund_foo_txid
+ rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219)
+ rawtx_input_1 = {}
+ rawtx_input_1["txid"] = fund_bar_txid
+ rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29)
+ inputs = [rawtx_input_0, rawtx_input_1]
+ change_address = self.nodes[0].getnewaddress()
outputs = {}
- outputs[change_address] = 40
- outputs[node1_address] = 1210
+ outputs[node1_address] = 1240
+ outputs[change_address] = 1248 - 1240 + doublespend_fee
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)
+ # Create two spends using 1 50 BTC coin each
+ txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
# Have node0 mine a block:
@@ -64,16 +76,16 @@ class TxnMallTest(BitcoinTestFramework):
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
+ # matured block, minus 40, minus 20, and minus transaction fees:
+ expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
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"])
+ assert_equal(self.nodes[0].getbalance("foo", 0), 1219+tx1["amount"]+tx1["fee"])
+ assert_equal(self.nodes[0].getbalance("bar", 0), 29+tx2["amount"]+tx2["fee"])
if self.options.mine_block:
assert_equal(tx1["confirmations"], 1)
@@ -84,8 +96,10 @@ class TxnMallTest(BitcoinTestFramework):
assert_equal(tx1["confirmations"], 0)
assert_equal(tx2["confirmations"], 0)
- # Now give doublespend to miner:
- mutated_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
+ # Now give doublespend and its parents to miner:
+ self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
+ self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
+ self.nodes[2].sendrawtransaction(doublespend["hex"])
# ... mine a block...
self.nodes[2].generate(1)
@@ -103,17 +117,28 @@ class TxnMallTest(BitcoinTestFramework):
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
+ # two more matured blocks, minus 1240 for the double-spend, plus fees (which are
+ # negative):
+ expected = starting_balance + 100 - 1240 + fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee
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)
+ # Final "" balance is starting_balance - amount moved to accounts - doublespend + subsidies +
+ # fees (which are negative)
+ assert_equal(self.nodes[0].getbalance("foo"), 1219)
+ assert_equal(self.nodes[0].getbalance("bar"), 29)
+ assert_equal(self.nodes[0].getbalance(""), starting_balance
+ -1219
+ - 29
+ -1240
+ + 100
+ + fund_foo_tx["fee"]
+ + fund_bar_tx["fee"]
+ + doublespend_fee)
+
+ # Node1's "from0" account balance should be just the doublespend:
+ assert_equal(self.nodes[1].getbalance("from0"), 1240)
if __name__ == '__main__':
TxnMallTest().main()
+