add support for existing vins for fundrawtransaction

This commit is contained in:
Jonas Schnelli 2014-12-20 21:49:31 +01:00 committed by Matt Corallo
parent 018828d666
commit cd42202ac3
4 changed files with 341 additions and 47 deletions

View file

@ -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__':

View file

@ -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;

View file

@ -1334,27 +1334,73 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
return true;
}
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, vector<CTxIn> vPresetVINs, const CCoinControl* coinControl) const
{
vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
// create a empty set to store possible VINS
set<pair<const CWalletTx*,unsigned int> > 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<COutput>::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<pair<CScript, CAmount> > vecSend;
vector<CTxIn> 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<pair<CScript, CAmount> >& vecSend,
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
{
vector<CTxIn> vINs;
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign);
}
bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, const vector<CTxIn> 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<pair<CScript, CAmount> >& vecSend,
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > 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<CScript, CAmount> > vecSend;
vecSend.push_back(make_pair(scriptPubKey, nValue));
CMutableTransaction txNew;
return CreateTransaction(vecSend, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign);
vector<CTxIn> vINs;
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign);
}
/**

View file

@ -110,7 +110,7 @@ public:
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
private:
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const std::vector<CTxIn> 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<std::pair<CScript, CAmount> >& vecSend, const std::vector<CTxIn> vins,
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& 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;