Merge pull request #6088
2085895fundrawtransaction tests (Jonas Schnelli)21bbd92Add fundrawtransaction RPC method (Matt Corallo)1e0d1a2Add FundTransaction method to wallet (Matt Corallo)2d84e22Small tweaks to CCoinControl for fundrawtransaction (Matt Corallo)9b4e7d9Add DummySignatureCreator which just creates zeroed sigs (Pieter Wuille)
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "signrawtransaction", 1 },
|
||||
{ "signrawtransaction", 2 },
|
||||
{ "sendrawtransaction", 1 },
|
||||
{ "fundrawtransaction", 1 },
|
||||
{ "gettxout", 1 },
|
||||
{ "gettxout", 2 },
|
||||
{ "gettxoutproof", 0 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user