Skip to content

Commit ddd8d80

Browse files
committed
Merge pull request #6415
d042854 SQUASH "Implement watchonly support in fundrawtransaction" (Matt Corallo) 428a898 SQUASH "Add have-pubkey distinction to ISMINE flags" (Matt Corallo) 6bdb474 Implement watchonly support in fundrawtransaction (Matt Corallo) f5813bd Add logic to track pubkeys as watch-only, not just scripts (Matt Corallo) d3354c5 Add have-pubkey distinction to ISMINE flags (Matt Corallo) 5c17059 Update importaddress help to push its use to script-only (Matt Corallo) a1d7df3 Add importpubkey method to import a watch-only pubkey (Matt Corallo) 907a425 Add p2sh option to importaddress to import redeemScripts (Matt Corallo) 983d2d9 Split up importaddress into helper functions (Matt Corallo) cfc3dd3 Also remove pay-2-pubkey from watch when adding a priv key (Matt Corallo)
2 parents 87f37e2 + d042854 commit ddd8d80

22 files changed

+272
-63
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

@@ -541,5 +552,45 @@ def run_test(self):
541552
assert_equal(len(dec_tx['vout']), 2) # one change output added
542553

543554

555+
##################################################
556+
# test a fundrawtransaction using only watchonly #
557+
##################################################
558+
559+
inputs = []
560+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount / 2}
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"]), 1)
566+
assert_equal(res_dec["vin"][0]["txid"], watchonly_txid)
567+
568+
assert_equal("fee" in result.keys(), True)
569+
assert_greater_than(result["changepos"], -1)
570+
571+
###############################################################
572+
# test fundrawtransaction using the entirety of watched funds #
573+
###############################################################
574+
575+
inputs = []
576+
outputs = {self.nodes[2].getnewaddress() : watchonly_amount}
577+
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
578+
579+
result = self.nodes[3].fundrawtransaction(rawtx, True)
580+
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
581+
assert_equal(len(res_dec["vin"]), 2)
582+
assert(res_dec["vin"][0]["txid"] == watchonly_txid or res_dec["vin"][1]["txid"] == watchonly_txid)
583+
584+
assert_greater_than(result["fee"], 0)
585+
assert_greater_than(result["changepos"], -1)
586+
assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], watchonly_amount / 10)
587+
588+
signedtx = self.nodes[3].signrawtransaction(result["hex"])
589+
assert(not signedtx["complete"])
590+
signedtx = self.nodes[0].signrawtransaction(signedtx["hex"])
591+
assert(signedtx["complete"])
592+
self.nodes[0].sendrawtransaction(signedtx["hex"])
593+
594+
544595
if __name__ == '__main__':
545596
RawTransactionsTest().main()

qa/rpc-tests/listtransactions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ def run_test(self):
9393
{"category":"receive","amount":Decimal("0.44")},
9494
{"txid":txid, "account" : "toself"} )
9595

96+
multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
97+
self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
98+
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
99+
self.nodes[1].generate(1)
100+
self.sync_all()
101+
assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0)
102+
check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True),
103+
{"category":"receive","amount":Decimal("0.1")},
104+
{"txid":txid, "account" : "watchonly"} )
105+
96106
if __name__ == '__main__':
97107
ListTransactionsTest().main()
98108

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_SOLVABLE 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/keystore.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,30 @@
66
#include "keystore.h"
77

88
#include "key.h"
9+
#include "pubkey.h"
910
#include "util.h"
1011

1112
#include <boost/foreach.hpp>
1213

13-
bool CKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
14+
bool CKeyStore::AddKey(const CKey &key) {
15+
return AddKeyPubKey(key, key.GetPubKey());
16+
}
17+
18+
bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
1419
{
1520
CKey key;
16-
if (!GetKey(address, key))
21+
if (!GetKey(address, key)) {
22+
WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
23+
if (it != mapWatchKeys.end()) {
24+
vchPubKeyOut = it->second;
25+
return true;
26+
}
1727
return false;
28+
}
1829
vchPubKeyOut = key.GetPubKey();
1930
return true;
2031
}
2132

22-
bool CKeyStore::AddKey(const CKey &key) {
23-
return AddKeyPubKey(key, key.GetPubKey());
24-
}
25-
2633
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
2734
{
2835
LOCK(cs_KeyStore);
@@ -58,17 +65,39 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
5865
return false;
5966
}
6067

68+
static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
69+
{
70+
//TODO: Use Solver to extract this?
71+
CScript::const_iterator pc = dest.begin();
72+
opcodetype opcode;
73+
std::vector<unsigned char> vch;
74+
if (!dest.GetOp(pc, opcode, vch) || vch.size() < 33 || vch.size() > 65)
75+
return false;
76+
pubKeyOut = CPubKey(vch);
77+
if (!pubKeyOut.IsFullyValid())
78+
return false;
79+
if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch))
80+
return false;
81+
return true;
82+
}
83+
6184
bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
6285
{
6386
LOCK(cs_KeyStore);
6487
setWatchOnly.insert(dest);
88+
CPubKey pubKey;
89+
if (ExtractPubKey(dest, pubKey))
90+
mapWatchKeys[pubKey.GetID()] = pubKey;
6591
return true;
6692
}
6793

6894
bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest)
6995
{
7096
LOCK(cs_KeyStore);
7197
setWatchOnly.erase(dest);
98+
CPubKey pubKey;
99+
if (ExtractPubKey(dest, pubKey))
100+
mapWatchKeys.erase(pubKey.GetID());
72101
return true;
73102
}
74103

src/keystore.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class CKeyStore
3232
virtual bool HaveKey(const CKeyID &address) const =0;
3333
virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0;
3434
virtual void GetKeys(std::set<CKeyID> &setAddress) const =0;
35-
virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
35+
virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const =0;
3636

3737
//! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki
3838
virtual bool AddCScript(const CScript& redeemScript) =0;
@@ -47,6 +47,7 @@ class CKeyStore
4747
};
4848

4949
typedef std::map<CKeyID, CKey> KeyMap;
50+
typedef std::map<CKeyID, CPubKey> WatchKeyMap;
5051
typedef std::map<CScriptID, CScript > ScriptMap;
5152
typedef std::set<CScript> WatchOnlySet;
5253

@@ -55,11 +56,13 @@ class CBasicKeyStore : public CKeyStore
5556
{
5657
protected:
5758
KeyMap mapKeys;
59+
WatchKeyMap mapWatchKeys;
5860
ScriptMap mapScripts;
5961
WatchOnlySet setWatchOnly;
6062

6163
public:
6264
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
65+
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
6366
bool HaveKey(const CKeyID &address) const
6467
{
6568
bool result;

src/qt/sendcoinsdialog.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,10 +754,9 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
754754
}
755755
else // Valid address
756756
{
757-
CPubKey pubkey;
758757
CKeyID keyid;
759758
addr.GetKeyID(keyid);
760-
if (!model->getPubKey(keyid, pubkey)) // Unknown change address
759+
if (!model->havePrivKey(keyid)) // Unknown change address
761760
{
762761
ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
763762
}

src/qt/transactiondesc.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
165165

166166
if (fAllFromMe)
167167
{
168-
if(fAllFromMe == ISMINE_WATCH_ONLY)
168+
if(fAllFromMe & ISMINE_WATCH_ONLY)
169169
strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
170170

171171
//
@@ -190,7 +190,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
190190
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
191191
if(toSelf == ISMINE_SPENDABLE)
192192
strHTML += " (own address)";
193-
else if(toSelf == ISMINE_WATCH_ONLY)
193+
else if(toSelf & ISMINE_WATCH_ONLY)
194194
strHTML += " (watch-only)";
195195
strHTML += "<br>";
196196
}

src/qt/transactionrecord.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
5656
CTxDestination address;
5757
sub.idx = parts.size(); // sequence number
5858
sub.credit = txout.nValue;
59-
sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY;
59+
sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
6060
if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address))
6161
{
6262
// Received by Bitcoin Address
@@ -86,15 +86,15 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
8686
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
8787
{
8888
isminetype mine = wallet->IsMine(txin);
89-
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
89+
if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
9090
if(fAllFromMe > mine) fAllFromMe = mine;
9191
}
9292

9393
isminetype fAllToMe = ISMINE_SPENDABLE;
9494
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
9595
{
9696
isminetype mine = wallet->IsMine(txout);
97-
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
97+
if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
9898
if(fAllToMe > mine) fAllToMe = mine;
9999
}
100100

src/qt/walletmodel.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,11 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
556556
return wallet->GetPubKey(address, vchPubKeyOut);
557557
}
558558

559+
bool WalletModel::havePrivKey(const CKeyID &address) const
560+
{
561+
return wallet->HaveKey(address);
562+
}
563+
559564
// returns a list of COutputs from COutPoints
560565
void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs)
561566
{

src/qt/walletmodel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ class WalletModel : public QObject
187187
UnlockContext requestUnlock();
188188

189189
bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
190+
bool havePrivKey(const CKeyID &address) const;
190191
void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs);
191192
bool isSpent(const COutPoint& outpoint) const;
192193
void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const;

0 commit comments

Comments
 (0)