Implement watchonly support in fundrawtransaction
Some code and test cases stolen from Bryan Bishop <bryan@ledgerx.com> (pull #5524).
This commit is contained in:
parent
f5813bdd3e
commit
6bdb474dc9
5 changed files with 78 additions and 12 deletions
|
@ -13,14 +13,15 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def setup_chain(self):
|
def setup_chain(self):
|
||||||
print("Initializing test directory "+self.options.tmpdir)
|
print("Initializing test directory "+self.options.tmpdir)
|
||||||
initialize_chain_clean(self.options.tmpdir, 3)
|
initialize_chain_clean(self.options.tmpdir, 4)
|
||||||
|
|
||||||
def setup_network(self, split=False):
|
def setup_network(self, split=False):
|
||||||
self.nodes = start_nodes(3, self.options.tmpdir)
|
self.nodes = start_nodes(4, self.options.tmpdir)
|
||||||
|
|
||||||
connect_nodes_bi(self.nodes,0,1)
|
connect_nodes_bi(self.nodes,0,1)
|
||||||
connect_nodes_bi(self.nodes,1,2)
|
connect_nodes_bi(self.nodes,1,2)
|
||||||
connect_nodes_bi(self.nodes,0,2)
|
connect_nodes_bi(self.nodes,0,2)
|
||||||
|
connect_nodes_bi(self.nodes,0,3)
|
||||||
|
|
||||||
self.is_network_split=False
|
self.is_network_split=False
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
@ -31,11 +32,20 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
self.nodes[2].generate(1)
|
self.nodes[2].generate(1)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
self.nodes[0].generate(101)
|
self.nodes[0].generate(121)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
|
watchonly_address = self.nodes[0].getnewaddress()
|
||||||
|
watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"]
|
||||||
|
watchonly_amount = 200
|
||||||
|
self.nodes[3].importpubkey(watchonly_pubkey, "", True)
|
||||||
|
watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
|
||||||
|
self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10);
|
||||||
|
|
||||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5);
|
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(),1.0);
|
||||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
|
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
|
||||||
|
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
@ -428,11 +438,12 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
stop_nodes(self.nodes)
|
stop_nodes(self.nodes)
|
||||||
wait_bitcoinds()
|
wait_bitcoinds()
|
||||||
|
|
||||||
self.nodes = start_nodes(3, self.options.tmpdir)
|
self.nodes = start_nodes(4, self.options.tmpdir)
|
||||||
|
|
||||||
connect_nodes_bi(self.nodes,0,1)
|
connect_nodes_bi(self.nodes,0,1)
|
||||||
connect_nodes_bi(self.nodes,1,2)
|
connect_nodes_bi(self.nodes,1,2)
|
||||||
connect_nodes_bi(self.nodes,0,2)
|
connect_nodes_bi(self.nodes,0,2)
|
||||||
|
connect_nodes_bi(self.nodes,0,3)
|
||||||
self.is_network_split=False
|
self.is_network_split=False
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
|
@ -525,5 +536,45 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
|
assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
|
||||||
|
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# test a fundrawtransaction using only watchonly #
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
inputs = []
|
||||||
|
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
|
||||||
|
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
|
||||||
|
|
||||||
|
result = self.nodes[3].fundrawtransaction(rawtx, True)
|
||||||
|
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
|
||||||
|
assert_equal(len(res_dec["vin"]), 1)
|
||||||
|
assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
|
||||||
|
|
||||||
|
assert_equal("fee" in result.keys(), True)
|
||||||
|
assert_greater_than(result["changepos"], -1)
|
||||||
|
|
||||||
|
###############################################################
|
||||||
|
# test fundrawtransaction using the entirety of watched funds #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
inputs = []
|
||||||
|
outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
|
||||||
|
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
|
||||||
|
|
||||||
|
result = self.nodes[3].fundrawtransaction(rawtx, True)
|
||||||
|
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
|
||||||
|
assert_equal(len(res_dec["vin"]), 2)
|
||||||
|
assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid)
|
||||||
|
|
||||||
|
assert_greater_than(result["fee"], 0)
|
||||||
|
assert_greater_than(result["changepos"], -1)
|
||||||
|
assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10)
|
||||||
|
|
||||||
|
signedtx = self.nodes[3].signrawtransaction(result["hex"])
|
||||||
|
assert(not signedtx["complete"])
|
||||||
|
signedtx = self.nodes[0].signrawtransaction(signedtx["hex"])
|
||||||
|
assert(signedtx["complete"])
|
||||||
|
self.nodes[0].sendrawtransaction(signedtx["hex"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
RawTransactionsTest().main()
|
RawTransactionsTest().main()
|
||||||
|
|
|
@ -14,6 +14,8 @@ public:
|
||||||
CTxDestination destChange;
|
CTxDestination destChange;
|
||||||
//! If false, allows unselected inputs, but requires all selected inputs be used
|
//! If false, allows unselected inputs, but requires all selected inputs be used
|
||||||
bool fAllowOtherInputs;
|
bool fAllowOtherInputs;
|
||||||
|
//! Includes watch only addresses which match the ISMINE_WATCH_PUBKEY criteria
|
||||||
|
bool fAllowWatchOnly;
|
||||||
|
|
||||||
CCoinControl()
|
CCoinControl()
|
||||||
{
|
{
|
||||||
|
@ -24,6 +26,7 @@ public:
|
||||||
{
|
{
|
||||||
destChange = CNoDestination();
|
destChange = CNoDestination();
|
||||||
fAllowOtherInputs = false;
|
fAllowOtherInputs = false;
|
||||||
|
fAllowWatchOnly = false;
|
||||||
setSelected.clear();
|
setSelected.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2367,15 +2367,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
|
|
||||||
if (fHelp || params.size() != 1)
|
if (fHelp || params.size() < 1 || params.size() > 2)
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"fundrawtransaction \"hexstring\"\n"
|
"fundrawtransaction \"hexstring\" includeWatching\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"
|
||||||
"The inputs added will not be signed, use signrawtransaction for that.\n"
|
"The inputs added will not be signed, use signrawtransaction for that.\n"
|
||||||
|
"Note that all existing inputs must have their previous output transaction be in the wallet.\n"
|
||||||
|
"Note that all inputs selected must be of standard form and P2SH scripts must be"
|
||||||
|
"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"
|
||||||
"\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"
|
||||||
"\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"
|
||||||
|
@ -2394,18 +2399,22 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
|
||||||
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
|
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
|
||||||
);
|
);
|
||||||
|
|
||||||
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
|
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
|
||||||
|
|
||||||
// parse hex string from parameter
|
// parse hex string from parameter
|
||||||
CTransaction origTx;
|
CTransaction origTx;
|
||||||
if (!DecodeHexTx(origTx, params[0].get_str()))
|
if (!DecodeHexTx(origTx, params[0].get_str()))
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
|
||||||
|
|
||||||
|
bool includeWatching = false;
|
||||||
|
if (params.size() > 1)
|
||||||
|
includeWatching = true;
|
||||||
|
|
||||||
CMutableTransaction tx(origTx);
|
CMutableTransaction tx(origTx);
|
||||||
CAmount nFee;
|
CAmount nFee;
|
||||||
string strFailReason;
|
string strFailReason;
|
||||||
int nChangePos = -1;
|
int nChangePos = -1;
|
||||||
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason))
|
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching))
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
|
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
|
|
|
@ -1524,7 +1524,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
|
||||||
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
|
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
|
||||||
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
|
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
|
||||||
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || 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));
|
vCoins.push_back(COutput(pcoin, i, nDepth,
|
||||||
|
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
|
||||||
|
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_PUBKEY) != ISMINE_NO)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1740,7 +1742,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
|
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching)
|
||||||
{
|
{
|
||||||
vector<CRecipient> vecSend;
|
vector<CRecipient> vecSend;
|
||||||
|
|
||||||
|
@ -1753,6 +1755,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
|
||||||
|
|
||||||
CCoinControl coinControl;
|
CCoinControl coinControl;
|
||||||
coinControl.fAllowOtherInputs = true;
|
coinControl.fAllowOtherInputs = true;
|
||||||
|
coinControl.fAllowWatchOnly = includeWatching;
|
||||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||||
coinControl.Select(txin.prevout);
|
coinControl.Select(txin.prevout);
|
||||||
|
|
||||||
|
|
|
@ -630,7 +630,7 @@ public:
|
||||||
CAmount GetWatchOnlyBalance() const;
|
CAmount GetWatchOnlyBalance() const;
|
||||||
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
||||||
CAmount GetImmatureWatchOnlyBalance() const;
|
CAmount GetImmatureWatchOnlyBalance() const;
|
||||||
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason);
|
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
|
||||||
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& nChangePosRet,
|
||||||
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);
|
||||||
|
|
Loading…
Reference in a new issue