aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xqa/pull-tester/rpc-tests.sh1
-rwxr-xr-xqa/rpc-tests/fundrawtransaction.py556
-rw-r--r--src/coincontrol.h5
-rw-r--r--src/rpcclient.cpp1
-rw-r--r--src/rpcserver.cpp3
-rw-r--r--src/rpcserver.h1
-rw-r--r--src/script/sign.cpp36
-rw-r--r--src/script/sign.h8
-rw-r--r--src/test/rpc_wallet_tests.cpp6
-rw-r--r--src/wallet/rpcwallet.cpp54
-rw-r--r--src/wallet/wallet.cpp125
-rw-r--r--src/wallet/wallet.h5
12 files changed, 787 insertions, 14 deletions
diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh
index 426d81fa90..d9a72fae2d 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 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/src/coincontrol.h b/src/coincontrol.h
index 92fae9847c..3e8de83c39 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<COutPoint>& vOutpoints)
+ void ListSelected(std::vector<COutPoint>& vOutpoints) const
{
vOutpoints.assign(setSelected.begin(), setSelected.end());
}
diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp
index 1d94e4f61b..4c6b47e4a0 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 6d089c6738..2f28971589 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 d08ae72f5c..45af10c2a8 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 eab629cd91..4543ca303f 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<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode) const
+ {
+ return true;
+ }
+};
+const DummySignatureChecker dummyChecker;
+}
+
+const BaseSignatureChecker& DummySignatureCreator::Checker() const
+{
+ return dummyChecker;
+}
+
+bool DummySignatureCreator::CreateSig(std::vector<unsigned char>& 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 0c4cf61e5e..13f45007dd 100644
--- a/src/script/sign.h
+++ b/src/script/sign.h
@@ -43,6 +43,14 @@ public:
bool CreateSig(std::vector<unsigned char>& 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<unsigned char>& 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 a72b656100..9368963ff2 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 5404dd4aa0..8d88933878 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 3f12d88e79..eee57900b5 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1509,7 +1509,7 @@ void CWallet::AvailableCoins(vector<COutput>& 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<pair<const CWalletTx*
AvailableCoins(vCoins, true, coinControl);
// coin control -> 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<pair<const CWalletTx*, uint32_t> > setPresetCoins;
+ CAmount nValueFromPresetInputs = 0;
+
+ std::vector<COutPoint> vPresetInputs;
+ if (coinControl)
+ coinControl->ListSelected(vPresetInputs);
+ BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs)
+ {
+ map<uint256, CWalletTx>::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<COutput>::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<CRecipient>& 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<CRecipient> 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<CRecipient>& 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<CRecipient>& 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<CTransaction*>(&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 9f3f08d117..b6a8e8671f 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<CRecipient>& 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<CRecipient>& 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;