diff options
Diffstat (limited to 'qa/rpc-tests')
-rw-r--r-- | qa/rpc-tests/README.md | 2 | ||||
-rwxr-xr-x | qa/rpc-tests/decodescript.py | 116 | ||||
-rwxr-xr-x | qa/rpc-tests/fundrawtransaction.py | 556 | ||||
-rwxr-xr-x | qa/rpc-tests/httpbasics.py | 34 | ||||
-rwxr-xr-x | qa/rpc-tests/nodehandling.py | 87 | ||||
-rwxr-xr-x | qa/rpc-tests/proxy_test.py | 62 | ||||
-rwxr-xr-x | qa/rpc-tests/txn_clone.py | 167 | ||||
-rwxr-xr-x | qa/rpc-tests/txn_doublespend.py | 79 |
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() + |