Skip to content

Commit 6bdb474

Browse files
committed
Implement watchonly support in fundrawtransaction
Some code and test cases stolen from Bryan Bishop <[email protected]> (pull #5524).
1 parent f5813bd commit 6bdb474

File tree

5 files changed

+78
-12
lines changed

5 files changed

+78
-12
lines changed

qa/rpc-tests/fundrawtransaction.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ class RawTransactionsTest(BitcoinTestFramework):
1313

1414
def setup_chain(self):
1515
print("Initializing test directory "+self.options.tmpdir)
16-
initialize_chain_clean(self.options.tmpdir, 3)
16+
initialize_chain_clean(self.options.tmpdir, 4)
1717

1818
def setup_network(self, split=False):
19-
self.nodes = start_nodes(3, self.options.tmpdir)
19+
self.nodes = start_nodes(4, self.options.tmpdir)
2020

2121
connect_nodes_bi(self.nodes,0,1)
2222
connect_nodes_bi(self.nodes,1,2)
2323
connect_nodes_bi(self.nodes,0,2)
24+
connect_nodes_bi(self.nodes,0,3)
2425

2526
self.is_network_split=False
2627
self.sync_all()
@@ -31,11 +32,20 @@ def run_test(self):
3132

3233
self.nodes[2].generate(1)
3334
self.sync_all()
34-
self.nodes[0].generate(101)
35+
self.nodes[0].generate(121)
3536
self.sync_all()
37+
38+
watchonly_address = self.nodes[0].getnewaddress()
39+
watchonly_pubkey = self.nodes[0].validateaddress(watchonly_address)["pubkey"]
40+
watchonly_amount = 200
41+
self.nodes[3].importpubkey(watchonly_pubkey, "", True)
42+
watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
43+
self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10);
44+
3645
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5);
3746
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0);
3847
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
48+
3949
self.sync_all()
4050
self.nodes[0].generate(1)
4151
self.sync_all()
@@ -428,11 +438,12 @@ def run_test(self):
428438
stop_nodes(self.nodes)
429439
wait_bitcoinds()
430440

431-
self.nodes = start_nodes(3, self.options.tmpdir)
441+
self.nodes = start_nodes(4, self.options.tmpdir)
432442

433443
connect_nodes_bi(self.nodes,0,1)
434444
connect_nodes_bi(self.nodes,1,2)
435445
connect_nodes_bi(self.nodes,0,2)
446+
connect_nodes_bi(self.nodes,0,3)
436447
self.is_network_split=False
437448
self.sync_all()
438449

@@ -525,5 +536,45 @@ def run_test(self):
525536
assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward
526537

527538

539+
##################################################
540+
# test a fundrawtransaction using only watchonly #
541+
##################################################
542+
543+
inputs = []
544+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
545+
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
546+
547+
result = self.nodes[3].fundrawtransaction(rawtx, True)
548+
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
549+
assert_equal(len(res_dec["vin"]), 1)
550+
assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
551+
552+
assert_equal("fee" in result.keys(), True)
553+
assert_greater_than(result["changepos"], -1)
554+
555+
###############################################################
556+
# test fundrawtransaction using the entirety of watched funds #
557+
###############################################################
558+
559+
inputs = []
560+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
561+
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
562+
563+
result = self.nodes[3].fundrawtransaction(rawtx, True)
564+
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
565+
assert_equal(len(res_dec["vin"]), 2)
566+
assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid)
567+
568+
assert_greater_than(result["fee"], 0)
569+
assert_greater_than(result["changepos"], -1)
570+
assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10)
571+
572+
signedtx = self.nodes[3].signrawtransaction(result["hex"])
573+
assert(not signedtx["complete"])
574+
signedtx = self.nodes[0].signrawtransaction(signedtx["hex"])
575+
assert(signedtx["complete"])
576+
self.nodes[0].sendrawtransaction(signedtx["hex"])
577+
578+
528579
if __name__ == '__main__':
529580
RawTransactionsTest().main()

src/coincontrol.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class CCoinControl
1414
CTxDestination destChange;
1515
//! If false, allows unselected inputs, but requires all selected inputs be used
1616
bool fAllowOtherInputs;
17+
//! Includes watch only addresses which match the ISMINE_WATCH_PUBKEY criteria
18+
bool fAllowWatchOnly;
1719

1820
CCoinControl()
1921
{
@@ -24,6 +26,7 @@ class CCoinControl
2426
{
2527
destChange = CNoDestination();
2628
fAllowOtherInputs = false;
29+
fAllowWatchOnly = false;
2730
setSelected.clear();
2831
}
2932

src/wallet/rpcwallet.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,15 +2367,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
23672367
if (!EnsureWalletIsAvailable(fHelp))
23682368
return NullUniValue;
23692369

2370-
if (fHelp || params.size() != 1)
2370+
if (fHelp || params.size() < 1 || params.size() > 2)
23712371
throw runtime_error(
2372-
"fundrawtransaction \"hexstring\"\n"
2372+
"fundrawtransaction \"hexstring\" includeWatching\n"
23732373
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
23742374
"This will not modify existing inputs, and will add one change output to the outputs.\n"
23752375
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
23762376
"The inputs added will not be signed, use signrawtransaction for that.\n"
2377+
"Note that all existing inputs must have their previous output transaction be in the wallet.\n"
2378+
"Note that all inputs selected must be of standard form and P2SH scripts must be"
2379+
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
2380+
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
23772381
"\nArguments:\n"
2378-
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
2382+
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
2383+
"2. includeWatching (boolean, optional, default false) Also select inputs which are watch only\n"
23792384
"\nResult:\n"
23802385
"{\n"
23812386
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
@@ -2394,18 +2399,22 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
23942399
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
23952400
);
23962401

2397-
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
2402+
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
23982403

23992404
// parse hex string from parameter
24002405
CTransaction origTx;
24012406
if (!DecodeHexTx(origTx, params[0].get_str()))
24022407
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
24032408

2409+
bool includeWatching = false;
2410+
if (params.size() > 1)
2411+
includeWatching = true;
2412+
24042413
CMutableTransaction tx(origTx);
24052414
CAmount nFee;
24062415
string strFailReason;
24072416
int nChangePos = -1;
2408-
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason))
2417+
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching))
24092418
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
24102419

24112420
UniValue result(UniValue::VOBJ);

src/wallet/wallet.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,7 +1524,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
15241524
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
15251525
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
15261526
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i)))
1527-
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
1527+
vCoins.push_back(COutput(pcoin, i, nDepth,
1528+
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
1529+
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_PUBKEY) != ISMINE_NO)));
15281530
}
15291531
}
15301532
}
@@ -1740,7 +1742,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
17401742
return res;
17411743
}
17421744

1743-
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason)
1745+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching)
17441746
{
17451747
vector<CRecipient> vecSend;
17461748

@@ -1753,6 +1755,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
17531755

17541756
CCoinControl coinControl;
17551757
coinControl.fAllowOtherInputs = true;
1758+
coinControl.fAllowWatchOnly = includeWatching;
17561759
BOOST_FOREACH(const CTxIn& txin, tx.vin)
17571760
coinControl.Select(txin.prevout);
17581761

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
630630
CAmount GetWatchOnlyBalance() const;
631631
CAmount GetUnconfirmedWatchOnlyBalance() const;
632632
CAmount GetImmatureWatchOnlyBalance() const;
633-
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason);
633+
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
634634
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
635635
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
636636
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);

0 commit comments

Comments
 (0)