From cd42202ac360c39404dbab01e3f4e360369af307 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Sat, 20 Dec 2014 21:49:31 +0100 Subject: [PATCH] add support for existing vins for `fundrawtransaction` --- qa/rpc-tests/rawtransactions.py | 293 ++++++++++++++++++++++++++++---- src/rpcrawtransaction.cpp | 3 - src/wallet.cpp | 84 +++++++-- src/wallet.h | 8 +- 4 files changed, 341 insertions(+), 47 deletions(-) diff --git a/qa/rpc-tests/rawtransactions.py b/qa/rpc-tests/rawtransactions.py index 2e20a5d89e..d4ad281651 100755 --- a/qa/rpc-tests/rawtransactions.py +++ b/qa/rpc-tests/rawtransactions.py @@ -10,42 +10,277 @@ from test_framework import BitcoinTestFramework from 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 to a local machine for debugging + # url = "http://bitcoinrpc:DP6DvqZtqXarpeNWyN3LZTFchCCyCUuHwNF7E8pX99x1@%s:%d" % ('127.0.0.1', 18332) + # proxy = AuthServiceProxy(url) + # proxy.url = url # store URL on proxy for info + # self.nodes.append(proxy) + + 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): - newAddr = self.nodes[2].getnewaddress() - - inputs = [] - outputs = { newAddr : 10 } - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - rawtxfund = self.nodes[0].fundrawtransaction(rawtx) - dec_rawtxfund = self.nodes[0].decoderawtransaction(rawtxfund['hex']) - - assert_equal(len(dec_rawtxfund['vin']), 1) - assert_equal(len(dec_rawtxfund['vout']), 2) - assert_equal(rawtxfund['fee'] > 0, True) - assert_equal(dec_rawtxfund['vin'][0]['scriptSig']['hex'], '') - - rawtxfundsigned = self.nodes[0].signrawtransaction(rawtxfund['hex']) - dec_rawtxfundsigned = self.nodes[0].decoderawtransaction(rawtxfundsigned['hex']) - - assert_equal(len(dec_rawtxfundsigned['vin'][0]['scriptSig']['hex']) > 20, True) - - - listunspent = self.nodes[2].listunspent() - inputs = [{'txid' : listunspent[0]['txid'], 'vout' : listunspent[0]['vout']}] - outputs = { newAddr : 10 } - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - aException = False - try: - rawtxfund = self.nodes[0].fundrawtransaction(rawtx) - except JSONRPCException,e: - aException = True + self.nodes[2].setgenerate(True, 1) + self.nodes[0].setgenerate(True, 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].setgenerate(True, 1) + self.sync_all() - assert_equal(aException, True) + ############### + # 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(fee*0.00000001+float(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(fee*0.00000001+float(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(fee*0.00000001+float(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(fee*0.00000001+float(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*0.00000001+float(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) + 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) + + assert_equal(fee*0.00000001+float(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*0.00000001+float(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*0.00000001+float(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); if __name__ == '__main__': diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index c73bc40c8f..9eb5897949 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -793,9 +793,6 @@ Value fundrawtransaction(const Array& params, bool fHelp) if (!DecodeHexTx(tx, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - if (tx.vin.size() > 0) - throw JSONRPCError(RPC_INVALID_PARAMETER, "fundrawtransaction only supports transactions with zero exiting vins"); - CMutableTransaction txNew; CAmount nFeeRet; string strFailReason; diff --git a/src/wallet.cpp b/src/wallet.cpp index 25ce41a117..576f0bda14 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1334,27 +1334,73 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int return true; } -bool CWallet::SelectCoins(const CAmount& nTargetValue, set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const +bool CWallet::SelectCoins(const CAmount& nTargetValue, set >& setCoinsRet, CAmount& nValueRet, vector vPresetVINs, const CCoinControl* coinControl) const { vector vCoins; AvailableCoins(vCoins, true, coinControl); - + + // create a empty set to store possible VINS + set > setTempCoins; + CAmount nValueTroughVINs = 0; + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) if (coinControl && coinControl->HasSelected()) { 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); } + + // fill up the tx with possible predefined VINs + BOOST_FOREACH(const CTxIn& txin, vPresetVINs) + { + bool vinOk = false; + // search for VIN in available coins + for (vector::iterator it = vCoins.begin() ; it != vCoins.end();) + { + const COutput& out = *it; + if (out.tx->GetHash() == txin.prevout.hash && txin.prevout.n == (uint32_t)out.i) + { + if (!out.fSpendable) + continue; + + nValueTroughVINs += out.tx->vout[out.i].nValue; + + // temporary keep the coin to add them later after SelectCoinsMinConf has added some + setTempCoins.insert(make_pair(out.tx, out.i)); + vinOk = true; + + // remove the coins from available coins vector to avoid double use because of a upcomming SelectCoinsMinConf + it = vCoins.erase(it); + } + else + ++it; + } + + if (!vinOk) + return false; // if vin was not an available coin, cancel (will return "Insufficient funds") + } - return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); + bool state = true; + + // only select further coins if we need to + if (nTargetValue-nValueTroughVINs > 0) + state = (SelectCoinsMinConf(nTargetValue-nValueTroughVINs, 1, 6, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue-nValueTroughVINs, 1, 1, vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue-nValueTroughVINs, 0, 1, vCoins, setCoinsRet, nValueRet))); + + // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible VINs to the coinset + setCoinsRet.insert(setTempCoins.begin(), setTempCoins.end()); + + // increase return value due of possible vins + nValueRet+=nValueTroughVINs; + + return state; } @@ -1362,19 +1408,32 @@ bool CWallet::FundTransaction(const CTransaction& txToFund, CMutableTransaction& { vector > vecSend; + vector vin; - BOOST_FOREACH (const CTxOut& out, txToFund.vout) + BOOST_FOREACH (const CTxOut& txOut, txToFund.vout) { - vecSend.push_back(make_pair(out.scriptPubKey, out.nValue)); + vecSend.push_back(make_pair(txOut.scriptPubKey, txOut.nValue)); + } + + BOOST_FOREACH (const CTxIn& txIn, txToFund.vin) + { + vin.push_back(txIn); } CReserveKey reservekey(this); CWalletTx wtx; - return CreateTransaction(vecSend, wtx, txNew, reservekey, nFeeRet, strFailReason, NULL, false); + return CreateTransaction(vecSend, vin, wtx, txNew, reservekey, nFeeRet, strFailReason, NULL, false); } bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) +{ + vector vINs; + return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign); +} + +bool CWallet::CreateTransaction(const vector >& vecSend, const vector vINs, + CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) { CAmount nValue = 0; BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) @@ -1422,7 +1481,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, // Choose coins to use set > setCoins; CAmount nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) + if (!SelectCoins(nTotalValue, setCoins, nValueIn, vINs, coinControl)) { strFailReason = _("Insufficient funds"); return false; @@ -1566,7 +1625,8 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); CMutableTransaction txNew; - return CreateTransaction(vecSend, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign); + vector vINs; + return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign); } /** diff --git a/src/wallet.h b/src/wallet.h index 88c541f35c..5d66869330 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -110,7 +110,7 @@ public: class CWallet : public CCryptoKeyStore, public CValidationInterface { private: - bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const; + bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const std::vector vPresetVINs, const CCoinControl *coinControl = NULL) const; CWalletDB *pwalletdbEncryption; @@ -289,10 +289,12 @@ public: CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; bool FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount& nFeeRet, std::string& strFailReason); + bool CreateTransaction(const std::vector >& vecSend, const std::vector vins, + CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CreateTransaction(const std::vector >& vecSend, - CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = false); + CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = false); + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); static CFeeRate minTxFee;