Add change options to fundrawtransaction

This commit is contained in:
João Barbosa 2016-03-30 02:04:22 +01:00 committed by Wladimir J. van der Laan
parent 41e835dd50
commit af4fe7fd12
4 changed files with 153 additions and 24 deletions

View file

@ -177,6 +177,83 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee
####################################################
# test a fundrawtransaction with an invalid option #
####################################################
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(4.0) }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
try:
self.nodes[2].fundrawtransaction(rawtx, {'foo': 'bar'})
raise AssertionError("Accepted invalid option foo")
except JSONRPCException,e:
assert("Unexpected key foo" in e.error['message'])
############################################################
# test a fundrawtransaction with an invalid change address #
############################################################
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(4.0) }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
try:
self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': 'foobar'})
raise AssertionError("Accepted invalid bitcoin address")
except JSONRPCException,e:
assert("changeAddress must be a valid bitcoin address" in e.error['message'])
############################################################
# test a fundrawtransaction with a provided change address #
############################################################
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(4.0) }
rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
dec_tx = self.nodes[2].decoderawtransaction(rawtx)
assert_equal(utx['txid'], dec_tx['vin'][0]['txid'])
change = self.nodes[2].getnewaddress()
rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 0})
dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex'])
out = dec_tx['vout'][0];
assert_equal(change, out['scriptPubKey']['addresses'][0])
######################################################################### #########################################################################
# test a fundrawtransaction with a VIN smaller than the required amount # # test a fundrawtransaction with a VIN smaller than the required amount #
######################################################################### #########################################################################
@ -568,7 +645,7 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2} outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs) rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
result = self.nodes[3].fundrawtransaction(rawtx, True) result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True })
res_dec = self.nodes[0].decoderawtransaction(result["hex"]) res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 1) assert_equal(len(res_dec["vin"]), 1)
assert_equal(res_dec["vin"][0]["txid"], watchonly_txid) assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
@ -584,6 +661,7 @@ class RawTransactionsTest(BitcoinTestFramework):
outputs = {self.nodes[2].getnewaddress() : watchonly_amount} outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
rawtx = self.nodes[3].createrawtransaction(inputs, outputs) rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
# Backward compatibility test (2nd param is includeWatching)
result = self.nodes[3].fundrawtransaction(rawtx, True) result = self.nodes[3].fundrawtransaction(rawtx, True)
res_dec = self.nodes[0].decoderawtransaction(result["hex"]) res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 2) assert_equal(len(res_dec["vin"]), 2)

View file

@ -2437,7 +2437,7 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 1 || params.size() > 2) if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error( throw runtime_error(
"fundrawtransaction \"hexstring\" includeWatching\n" "fundrawtransaction \"hexstring\" ( options )\n"
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\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" "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" "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
@ -2447,8 +2447,14 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n" "1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
"2. includeWatching (boolean, optional, default false) Also select inputs which are watch only\n" "2. options (object, optional)\n"
" {\n"
" \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
" }\n"
" for backward compatibility: passing in a true instzead of an object will result in {\"includeWatching\":true}\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
@ -2467,7 +2473,40 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
); );
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)); RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
CTxDestination changeAddress = CNoDestination();
int changePosition = -1;
bool includeWatching = false;
if (params.size() > 1) {
if (params[1].type() == UniValue::VBOOL) {
// backward compatibility bool only fallback
includeWatching = params[1].get_bool();
}
else {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ));
UniValue options = params[1];
RPCTypeCheckObj(options, boost::assign::map_list_of("changeAddress", UniValue::VSTR)("changePosition", UniValue::VNUM)("includeWatching", UniValue::VBOOL), true, true);
if (options.exists("changeAddress")) {
CBitcoinAddress address(options["changeAddress"].get_str());
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_PARAMETER, "changeAddress must be a valid bitcoin address");
changeAddress = address.Get();
}
if (options.exists("changePosition"))
changePosition = options["changePosition"].get_int();
if (options.exists("includeWatching"))
includeWatching = options["includeWatching"].get_bool();
}
}
// parse hex string from parameter // parse hex string from parameter
CTransaction origTx; CTransaction origTx;
@ -2477,20 +2516,19 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
if (origTx.vout.size() == 0) if (origTx.vout.size() == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
bool includeWatching = false; if (changePosition != -1 && (changePosition < 0 || changePosition > origTx.vout.size()))
if (params.size() > 1) throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
includeWatching = params[1].get_bool();
CMutableTransaction tx(origTx); CMutableTransaction tx(origTx);
CAmount nFee; CAmount nFee;
string strFailReason; string strFailReason;
int nChangePos = -1;
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching)) if(!pwalletMain->FundTransaction(tx, nFee, changePosition, strFailReason, includeWatching, changeAddress))
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
result.push_back(Pair("hex", EncodeHexTx(tx))); result.push_back(Pair("hex", EncodeHexTx(tx)));
result.push_back(Pair("changepos", nChangePos)); result.push_back(Pair("changepos", changePosition));
result.push_back(Pair("fee", ValueFromAmount(nFee))); result.push_back(Pair("fee", ValueFromAmount(nFee)));
return result; return result;

View file

@ -1932,7 +1932,7 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount&
return res; return res;
} }
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching) bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, const CTxDestination& destChange)
{ {
vector<CRecipient> vecSend; vector<CRecipient> vecSend;
@ -1944,6 +1944,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
} }
CCoinControl coinControl; CCoinControl coinControl;
coinControl.destChange = destChange;
coinControl.fAllowOtherInputs = true; coinControl.fAllowOtherInputs = true;
coinControl.fAllowWatchOnly = includeWatching; coinControl.fAllowWatchOnly = includeWatching;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
@ -1951,11 +1952,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
CReserveKey reservekey(this); CReserveKey reservekey(this);
CWalletTx wtx; CWalletTx wtx;
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false)) if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false))
return false; return false;
if (nChangePosRet != -1) if (nChangePosInOut != -1)
tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]); tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.vout[nChangePosInOut]);
// Add new txins (keeping original txin scriptSig/order) // Add new txins (keeping original txin scriptSig/order)
BOOST_FOREACH(const CTxIn& txin, wtx.vin) BOOST_FOREACH(const CTxIn& txin, wtx.vin)
@ -1968,9 +1969,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
} }
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
{ {
CAmount nValue = 0; CAmount nValue = 0;
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0; unsigned int nSubtractFeeFromAmount = 0;
BOOST_FOREACH (const CRecipient& recipient, vecSend) BOOST_FOREACH (const CRecipient& recipient, vecSend)
{ {
@ -2036,10 +2038,10 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// Start with no fee and loop until there is enough fee // Start with no fee and loop until there is enough fee
while (true) while (true)
{ {
nChangePosInOut = nChangePosRequest;
txNew.vin.clear(); txNew.vin.clear();
txNew.vout.clear(); txNew.vout.clear();
wtxNew.fFromMe = true; wtxNew.fFromMe = true;
nChangePosRet = -1;
bool fFirst = true; bool fFirst = true;
CAmount nValueToSelect = nValue; CAmount nValueToSelect = nValue;
@ -2159,14 +2161,24 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// add the dust to the fee. // add the dust to the fee.
if (newTxOut.IsDust(::minRelayTxFee)) if (newTxOut.IsDust(::minRelayTxFee))
{ {
nChangePosInOut = -1;
nFeeRet += nChange; nFeeRet += nChange;
reservekey.ReturnKey(); reservekey.ReturnKey();
} }
else else
{ {
// Insert change txn at random position: if (nChangePosInOut == -1)
nChangePosRet = GetRandInt(txNew.vout.size()+1); {
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet; // Insert change txn at random position:
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
}
else if (nChangePosInOut > txNew.vout.size())
{
strFailReason = _("Change index out of range");
return false;
}
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut); txNew.vout.insert(position, newTxOut);
} }
} }
@ -2842,13 +2854,13 @@ void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script)
script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
} }
void CWallet::LockCoin(COutPoint& output) void CWallet::LockCoin(const COutPoint& output)
{ {
AssertLockHeld(cs_wallet); // setLockedCoins AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.insert(output); setLockedCoins.insert(output);
} }
void CWallet::UnlockCoin(COutPoint& output) void CWallet::UnlockCoin(const COutPoint& output)
{ {
AssertLockHeld(cs_wallet); // setLockedCoins AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.erase(output); setLockedCoins.erase(output);

View file

@ -739,13 +739,14 @@ public:
* Insert additional inputs into the transaction by * Insert additional inputs into the transaction by
* calling CreateTransaction(); * calling CreateTransaction();
*/ */
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching); bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, const CTxDestination& destChange = CNoDestination());
/** /**
* Create a new transaction paying the recipients with a set of coins * Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed * selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random position
*/ */
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);