Skip to content

Commit aaf439b

Browse files
kanzureTheBlueMatt
authored andcommitted
Implement watchonly support in fundrawtransaction
Support watchonly in fundrawtransaction, CreateTransaction, SelectCoins and coin selection in general. Coin selection is exposed over RPC through fundrawtransaction, and watchonly can be enabled by passing includeWatching true (defaults to false). fundrawtransaction will first attempt to fund a transaction without using watchonly, even when includeWatching is enabled. When this fails, an includeWatching attempt is made. When an includeWatching attempt at coin selection is performed (in CreateTransaction), and all watchonlys are consumed leaving no funds available for a fee, then CreateTransaction will not include a fee in the transaction. Also, includeWatching tests (for fundrawtransaction). See also: bitcoin/bitcoin#5503
1 parent c5b204d commit aaf439b

File tree

4 files changed

+104
-34
lines changed

4 files changed

+104
-34
lines changed

qa/rpc-tests/rawtransactions.py

+38-2
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,26 @@ def setup_network(self, split=False):
3535

3636
self.is_network_split=False
3737
self.sync_all()
38-
38+
3939
def run_test(self):
4040

4141
self.nodes[2].setgenerate(True, 1)
42-
self.nodes[0].setgenerate(True, 101)
42+
self.nodes[0].setgenerate(True, 121)
4343
self.sync_all()
44+
45+
watchonly_address = self.nodes[0].getnewaddress()
46+
watchonly_amount = 200
47+
self.nodes[1].importaddress(watchonly_address, "", True)
48+
self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
49+
4450
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5);
4551
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0);
4652
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
53+
4754
self.sync_all()
55+
4856
self.nodes[0].setgenerate(True, 1)
57+
4958
self.sync_all()
5059

5160
###############
@@ -283,5 +292,32 @@ def run_test(self):
283292
assert_equal("Insufficient" in errorString, True);
284293

285294

295+
##################################################
296+
# test a fundrawtransaction using only watchonly #
297+
##################################################
298+
299+
inputs = []
300+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2.0}
301+
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
302+
303+
result = self.nodes[1].fundrawtransaction(rawtx, True)
304+
305+
assert_equal("hex" in result.keys(), True)
306+
assert_equal("fee" in result.keys(), True)
307+
308+
###############################################################
309+
# test fundrawtransaction using the entirety of watched funds #
310+
###############################################################
311+
312+
inputs = []
313+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 1.0}
314+
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
315+
316+
result = self.nodes[1].fundrawtransaction(rawtx, True)
317+
318+
assert_equal("hex" in result.keys(), True)
319+
assert_equal("fee" in result.keys(), True)
320+
321+
286322
if __name__ == '__main__':
287323
RawTransactionsTest().main()

src/rpcrawtransaction.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -760,13 +760,14 @@ Value sendrawtransaction(const Array& params, bool fHelp)
760760
#ifdef ENABLE_WALLET
761761
Value fundrawtransaction(const Array& params, bool fHelp)
762762
{
763-
if (fHelp || params.size() != 1)
763+
if (fHelp || params.size() < 1 || params.size() > 2)
764764
throw runtime_error(
765765
"fundrawtransaction \"hexstring\"\n"
766766
"\nAdd vIns to a raw transaction.\n"
767767
"\nAlso see createrawtransaction and signrawtransaction calls.\n"
768768
"\nArguments:\n"
769769
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
770+
"2. includeWatching (boolean, optional, default=false) Use watchonly outputs\n"
770771
"\nResult:\n"
771772
"{\n"
772773
" \"hex\": \"value\", (string) The raw transaction with vIns (hex-encoded string)\n"
@@ -792,11 +793,15 @@ Value fundrawtransaction(const Array& params, bool fHelp)
792793
CTransaction tx;
793794
if (!DecodeHexTx(tx, params[0].get_str()))
794795
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
795-
796+
797+
bool includeWatching = false;
798+
if (params.size() > 1)
799+
includeWatching = params[1].get_bool();
800+
796801
CMutableTransaction txNew;
797802
CAmount nFeeRet;
798803
string strFailReason;
799-
if(!pwalletMain->FundTransaction(tx, txNew, nFeeRet, strFailReason))
804+
if(!pwalletMain->FundTransaction(tx, txNew, nFeeRet, strFailReason, includeWatching))
800805
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
801806

802807
Object result;

src/wallet.cpp

+52-23
Original file line numberDiff line numberDiff line change
@@ -1152,7 +1152,7 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
11521152
/**
11531153
* populate vCoins with vector of available COutputs.
11541154
*/
1155-
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const
1155+
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool includeWatching) const
11561156
{
11571157
vCoins.clear();
11581158

@@ -1181,7 +1181,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
11811181
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
11821182
!IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 &&
11831183
(!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
1184-
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
1184+
vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (includeWatching && ((mine & ISMINE_WATCH_ONLY) != ISMINE_NO))));
11851185
}
11861186
}
11871187
}
@@ -1334,10 +1334,10 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
13341334
return true;
13351335
}
13361336

1337-
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, vector<CTxIn> vPresetVINs, const CCoinControl* coinControl) const
1337+
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, vector<CTxIn> vPresetVINs, const CCoinControl* coinControl, bool includeWatching) const
13381338
{
13391339
vector<COutput> vCoins;
1340-
AvailableCoins(vCoins, true, coinControl);
1340+
AvailableCoins(vCoins, true, coinControl, includeWatching);
13411341

13421342
// create a empty set to store possible VINS
13431343
set<pair<const CWalletTx*,unsigned int> > setTempCoins;
@@ -1404,37 +1404,48 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
14041404
}
14051405

14061406

1407-
bool CWallet::FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount &nFeeRet, std::string& strFailReason)
1407+
bool CWallet::FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount &nFeeRet, std::string& strFailReason, bool includeWatching)
14081408
{
1409-
1409+
14101410
vector<pair<CScript, CAmount> > vecSend;
14111411
vector<CTxIn> vin;
1412-
1412+
14131413
BOOST_FOREACH (const CTxOut& txOut, txToFund.vout)
14141414
{
14151415
vecSend.push_back(make_pair(txOut.scriptPubKey, txOut.nValue));
14161416
}
1417-
1417+
14181418
BOOST_FOREACH (const CTxIn& txIn, txToFund.vin)
14191419
{
14201420
vin.push_back(txIn);
14211421
}
1422-
1422+
14231423
CReserveKey reservekey(this);
14241424
CWalletTx wtx;
1425-
return CreateTransaction(vecSend, vin, wtx, txNew, reservekey, nFeeRet, strFailReason, NULL, false);
1425+
1426+
// always try first without including watchonly
1427+
bool result = CreateTransaction(vecSend, vin, wtx, txNew, reservekey, nFeeRet, strFailReason, NULL, false, false);
1428+
1429+
// There may be a solution including watchonly, which may also be a
1430+
// solution that does not pay any fees.
1431+
if (!result && includeWatching)
1432+
result = CreateTransaction(vecSend, vin, wtx, txNew, reservekey, nFeeRet, strFailReason, NULL, false, includeWatching);
1433+
1434+
return result;
14261435
}
14271436

14281437
bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
1429-
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
1438+
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching)
14301439
{
14311440
vector<CTxIn> vINs;
1432-
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign);
1441+
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching);
14331442
}
14341443

14351444
bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, const vector<CTxIn> vINs,
1436-
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
1445+
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching)
14371446
{
1447+
bool cannotFundFee = false; // used with includeWatching
1448+
14381449
CAmount nValue = 0;
14391450
BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend)
14401451
{
@@ -1481,10 +1492,22 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
14811492
// Choose coins to use
14821493
set<pair<const CWalletTx*,unsigned int> > setCoins;
14831494
CAmount nValueIn = 0;
1484-
if (!SelectCoins(nTotalValue, setCoins, nValueIn, vINs, coinControl))
1495+
if (!SelectCoins(nTotalValue, setCoins, nValueIn, vINs, coinControl, includeWatching))
14851496
{
1486-
strFailReason = _("Insufficient funds");
1487-
return false;
1497+
// fee selection is not mandatory when using includeWatching
1498+
if (includeWatching && SelectCoins(nTotalValue - nFeeRet, setCoins, nValueIn, vINs, coinControl, includeWatching) && nTotalValue >= nValue)
1499+
{
1500+
nFeeRet = 0;
1501+
1502+
// no more funds available for fee, but target met anyway
1503+
cannotFundFee = true;
1504+
}
1505+
1506+
else
1507+
{
1508+
strFailReason = _("Insufficient funds");
1509+
return false;
1510+
}
14881511
}
14891512
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
14901513
{
@@ -1554,10 +1577,11 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
15541577
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
15551578
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
15561579

1557-
// Sign
1580+
// Sign (also calculate fee)
15581581
int nIn = 0;
15591582
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
1560-
if (!SignSignature(*this, *coin.first, txNew, nIn++))
1583+
// when unsignable and watchonly enabled, this may be a watchonly so don't error
1584+
if (!SignSignature(*this, *coin.first, txNew, nIn++) && !includeWatching)
15611585
{
15621586
strFailReason = _("Signing transaction failed");
15631587
return false;
@@ -1573,14 +1597,19 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
15731597
strFailReason = _("Transaction too large");
15741598
return false;
15751599
}
1576-
1577-
//remove signature if we used the signing only for the fee calculation
1600+
1601+
// remove signature if we used the signing only for the fee calculation
15781602
if(!sign)
15791603
{
15801604
BOOST_FOREACH (CTxIn& vin, txNew.vin)
15811605
vin.scriptSig = CScript();
15821606
}
1583-
1607+
1608+
// There are no more available funds for funding a transaction
1609+
// fee, and includeWatching should de-prioritize fee funding.
1610+
if (includeWatching && cannotFundFee)
1611+
return true;
1612+
15841613
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
15851614

15861615
// Can we complete this as a free transaction?
@@ -1620,13 +1649,13 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend,
16201649
}
16211650

16221651
bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue,
1623-
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
1652+
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching)
16241653
{
16251654
vector< pair<CScript, CAmount> > vecSend;
16261655
vecSend.push_back(make_pair(scriptPubKey, nValue));
16271656
CMutableTransaction txNew;
16281657
vector<CTxIn> vINs;
1629-
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign);
1658+
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching);
16301659
}
16311660

16321661
/**

src/wallet.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class CAddressBookData
110110
class CWallet : public CCryptoKeyStore, public CValidationInterface
111111
{
112112
private:
113-
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const std::vector<CTxIn> vPresetVINs, const CCoinControl *coinControl = NULL) const;
113+
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const std::vector<CTxIn> vPresetVINs, const CCoinControl *coinControl = NULL, bool includeWatching = false) const;
114114

115115
CWalletDB *pwalletdbEncryption;
116116

@@ -205,7 +205,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
205205
//! check whether we are allowed to upgrade (or already support) to the named feature
206206
bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
207207

208-
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL) const;
208+
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool includeWatching = false) const;
209209
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
210210

211211
bool IsSpent(const uint256& hash, unsigned int n) const;
@@ -288,13 +288,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
288288
CAmount GetWatchOnlyBalance() const;
289289
CAmount GetUnconfirmedWatchOnlyBalance() const;
290290
CAmount GetImmatureWatchOnlyBalance() const;
291-
bool FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount& nFeeRet, std::string& strFailReason);
291+
bool FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount& nFeeRet, std::string& strFailReason, bool includeWatching = false);
292292
bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& vecSend, const std::vector<CTxIn> vins,
293-
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
293+
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false);
294294
bool CreateTransaction(const std::vector<std::pair<CScript, CAmount> >& vecSend,
295-
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
295+
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false);
296296
bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue,
297-
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
297+
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false);
298298
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
299299

300300
static CFeeRate minTxFee;

0 commit comments

Comments
 (0)