diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 426d81fa9..d9a72fae2 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -30,6 +30,7 @@ testScripts=( 'zapwallettxes.py' 'proxy_test.py' 'merkle_blocks.py' + 'fundrawtransaction.py' 'signrawtransactions.py' 'walletbackup.py' 'nodehandling.py' diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py new file mode 100755 index 000000000..e859b2643 --- /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/src/coincontrol.h b/src/coincontrol.h index 92fae9847..3e8de83c3 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -12,6 +12,8 @@ class CCoinControl { public: CTxDestination destChange; + //! If false, allows unselected inputs, but requires all selected inputs be used + bool fAllowOtherInputs; CCoinControl() { @@ -21,6 +23,7 @@ public: void SetNull() { destChange = CNoDestination(); + fAllowOtherInputs = false; setSelected.clear(); } @@ -50,7 +53,7 @@ public: setSelected.clear(); } - void ListSelected(std::vector& vOutpoints) + void ListSelected(std::vector& vOutpoints) const { vOutpoints.assign(setSelected.begin(), setSelected.end()); } diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 1d94e4f61..4c6b47e4a 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransaction", 1 }, { "signrawtransaction", 2 }, { "sendrawtransaction", 1 }, + { "fundrawtransaction", 1 }, { "gettxout", 1 }, { "gettxout", 2 }, { "gettxoutproof", 0 }, diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 6d089c673..2f2897158 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -320,6 +320,9 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "getrawtransaction", &getrawtransaction, true }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ +#ifdef ENABLE_WALLET + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, +#endif /* Utility functions */ { "util", "createmultisig", &createmultisig, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index d08ae72f5..45af10c2a 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -221,6 +221,7 @@ extern UniValue listlockunspent(const UniValue& params, bool fHelp); extern UniValue createrawtransaction(const UniValue& params, bool fHelp); extern UniValue decoderawtransaction(const UniValue& params, bool fHelp); extern UniValue decodescript(const UniValue& params, bool fHelp); +extern UniValue fundrawtransaction(const UniValue& params, bool fHelp); extern UniValue signrawtransaction(const UniValue& params, bool fHelp); extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); extern UniValue gettxoutproof(const UniValue& params, bool fHelp); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index eab629cd9..4543ca303 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -275,3 +275,39 @@ CScript CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecke return CombineSignatures(scriptPubKey, checker, txType, vSolutions, stack1, stack2); } + +namespace { +/** Dummy signature checker which accepts all signatures. */ +class DummySignatureChecker : public BaseSignatureChecker +{ +public: + DummySignatureChecker() {} + + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode) const + { + return true; + } +}; +const DummySignatureChecker dummyChecker; +} + +const BaseSignatureChecker& DummySignatureCreator::Checker() const +{ + return dummyChecker; +} + +bool DummySignatureCreator::CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const +{ + // Create a dummy signature that is a valid DER-encoding + vchSig.assign(72, '\000'); + vchSig[0] = 0x30; + vchSig[1] = 69; + vchSig[2] = 0x02; + vchSig[3] = 33; + vchSig[4] = 0x01; + vchSig[4 + 33] = 0x02; + vchSig[5 + 33] = 32; + vchSig[6 + 33] = 0x01; + vchSig[6 + 33 + 32] = SIGHASH_ALL; + return true; +} diff --git a/src/script/sign.h b/src/script/sign.h index 0c4cf61e5..13f45007d 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -43,6 +43,14 @@ public: bool CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const; }; +/** A signature creator that just produces 72-byte empty signatyres. */ +class DummySignatureCreator : public BaseSignatureCreator { +public: + DummySignatureCreator(const CKeyStore* keystoreIn) : BaseSignatureCreator(keystoreIn) {} + const BaseSignatureChecker& Checker() const; + bool CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const; +}; + /** Produce a script signature using a generic signature creator. */ bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& scriptPubKey, CScript& scriptSig); diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index a72b65610..9368963ff 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -217,6 +217,12 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) UniValue arr = retValue.get_array(); BOOST_CHECK(arr.size() > 0); BOOST_CHECK(CBitcoinAddress(arr[0].get_str()).Get() == demoAddress.Get()); + + /********************************* + * fundrawtransaction + *********************************/ + BOOST_CHECK_THROW(CallRPC("fundrawtransaction 28z"), runtime_error); + BOOST_CHECK_THROW(CallRPC("fundrawtransaction 01000000000180969800000000001976a91450ce0a4b0ee0ddeb633da85199728b940ac3fe9488ac00000000"), runtime_error); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5404dd4aa..8d8893387 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2361,3 +2361,57 @@ UniValue listunspent(const UniValue& params, bool fHelp) return results; } + +UniValue fundrawtransaction(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "fundrawtransaction \"hexstring\"\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add one change output to the outputs.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) The fee added to the transaction\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\"hex\" \n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + + // parse hex string from parameter + CTransaction origTx; + if (!DecodeHexTx(origTx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + + CMutableTransaction tx(origTx); + CAmount nFee; + string strFailReason; + int nChangePos = -1; + if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) + throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hex", EncodeHexTx(tx))); + result.push_back(Pair("changepos", nChangePos)); + result.push_back(Pair("fee", ValueFromAmount(nFee))); + + return result; +} diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3f12d88e7..eee57900b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1509,7 +1509,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const isminetype mine = IsMine(pcoin->vout[i]); if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); } } @@ -1669,25 +1669,108 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected()) + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) { BOOST_FOREACH(const COutput& out, vCoins) { - if(!out.fSpendable) - continue; + if (!out.fSpendable) + continue; nValueRet += out.tx->vout[out.i].nValue; setCoinsRet.insert(make_pair(out.tx, out.i)); } return (nValueRet >= nTargetValue); } - return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); + // calculate value from preset inputs and store them + set > setPresetCoins; + CAmount nValueFromPresetInputs = 0; + + std::vector vPresetInputs; + if (coinControl) + coinControl->ListSelected(vPresetInputs); + BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs) + { + map::const_iterator it = mapWallet.find(outpoint.hash); + if (it != mapWallet.end()) + { + const CWalletTx* pcoin = &it->second; + // Clearly invalid input, fail + if (pcoin->vout.size() <= outpoint.n) + return false; + nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue; + setPresetCoins.insert(make_pair(pcoin, outpoint.n)); + } else + return false; // TODO: Allow non-wallet inputs + } + + // remove preset inputs from vCoins + for (vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + { + if (setPresetCoins.count(make_pair(it->tx, it->i))) + it = vCoins.erase(it); + else + ++it; + } + + bool res = nTargetValue <= nValueFromPresetInputs || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, vCoins, setCoinsRet, nValueRet)); + + // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset + setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); + + // add preset inputs to the total value selected + nValueRet += nValueFromPresetInputs; + + return res; } -bool CWallet::CreateTransaction(const vector& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason) +{ + vector vecSend; + + // Turn the txout set into a CRecipient vector + BOOST_FOREACH(const CTxOut& txOut, tx.vout) + { + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false}; + vecSend.push_back(recipient); + } + + CCoinControl coinControl; + coinControl.fAllowOtherInputs = true; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + coinControl.Select(txin.prevout); + + CReserveKey reservekey(this); + CWalletTx wtx; + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false)) + return false; + + if (nChangePosRet != -1) + tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]); + + // Add new txins (keeping original txin scriptSig/order) + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + bool found = false; + BOOST_FOREACH(const CTxIn& origTxIn, tx.vin) + { + if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n) + { + found = true; + break; + } + } + if (!found) + tx.vin.push_back(txin); + } + + return true; +} + +bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) { CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0; @@ -1890,23 +1973,43 @@ bool CWallet::CreateTransaction(const vector& vecSend, // Sign int nIn = 0; + CTransaction txNewConst(txNew); BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) - if (!SignSignature(*this, *coin.first, txNew, nIn++)) + { + bool signSuccess; + const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey; + CScript& scriptSigRes = txNew.vin[nIn].scriptSig; + if (sign) + signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, SIGHASH_ALL), scriptPubKey, scriptSigRes); + else + signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, scriptSigRes); + + if (!signSuccess) { strFailReason = _("Signing transaction failed"); return false; } + nIn++; + } + + unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); + + // Remove scriptSigs if we used dummy signatures for fee calculation + if (!sign) { + BOOST_FOREACH (CTxIn& vin, txNew.vin) + vin.scriptSig = CScript(); + } // Embed the constructed transaction data in wtxNew. *static_cast(&wtxNew) = CTransaction(txNew); // Limit size - unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= MAX_STANDARD_TX_SIZE) { strFailReason = _("Transaction too large"); return false; } + dPriority = wtxNew.ComputePriority(dPriority, nBytes); // Can we complete this as a free transaction? diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9f3f08d11..b6a8e8671 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -625,8 +625,9 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; - bool CreateTransaction(const std::vector& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason); + bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, + std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); static CFeeRate minTxFee;