Skip to content

Commit

Permalink
Merge pull request bitcoin#6088
Browse files Browse the repository at this point in the history
2085895 fundrawtransaction tests (Jonas Schnelli)
21bbd92 Add fundrawtransaction RPC method (Matt Corallo)
1e0d1a2 Add FundTransaction method to wallet (Matt Corallo)
2d84e22 Small tweaks to CCoinControl for fundrawtransaction (Matt Corallo)
9b4e7d9 Add DummySignatureCreator which just creates zeroed sigs (Pieter Wuille)
  • Loading branch information
laanwj committed Jun 23, 2015
2 parents b77fbe0 + 2085895 commit 91389e5
Show file tree
Hide file tree
Showing 12 changed files with 787 additions and 14 deletions.
1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ testScripts=(
'zapwallettxes.py'
'proxy_test.py'
'merkle_blocks.py'
'fundrawtransaction.py'
'signrawtransactions.py'
'walletbackup.py'
'nodehandling.py'
Expand Down
556 changes: 556 additions & 0 deletions qa/rpc-tests/fundrawtransaction.py

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/coincontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class CCoinControl
{
public:
CTxDestination destChange;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;

CCoinControl()
{
Expand All @@ -21,6 +23,7 @@ class CCoinControl
void SetNull()
{
destChange = CNoDestination();
fAllowOtherInputs = false;
setSelected.clear();
}

Expand Down Expand Up @@ -50,7 +53,7 @@ class CCoinControl
setSelected.clear();
}

void ListSelected(std::vector<COutPoint>& vOutpoints)
void ListSelected(std::vector<COutPoint>& vOutpoints) const
{
vOutpoints.assign(setSelected.begin(), setSelected.end());
}
Expand Down
1 change: 1 addition & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "signrawtransaction", 1 },
{ "signrawtransaction", 2 },
{ "sendrawtransaction", 1 },
{ "fundrawtransaction", 1 },
{ "gettxout", 1 },
{ "gettxout", 2 },
{ "gettxoutproof", 0 },
Expand Down
3 changes: 3 additions & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
36 changes: 36 additions & 0 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
8 changes: 8 additions & 0 deletions src/script/sign.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ class TransactionSignatureCreator : public BaseSignatureCreator {
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);

Expand Down
6 changes: 6 additions & 0 deletions src/test/rpc_wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
54 changes: 54 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
125 changes: 114 additions & 11 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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?
Expand Down
5 changes: 3 additions & 2 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -625,8 +625,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
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;
Expand Down

0 comments on commit 91389e5

Please sign in to comment.