Skip to content

Commit

Permalink
Merge pull request #6415
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
laanwj committed Aug 14, 2015
2 parents 87f37e2 + d042854 commit ddd8d80
Show file tree
Hide file tree
Showing 22 changed files with 272 additions and 63 deletions.
59 changes: 55 additions & 4 deletions qa/rpc-tests/fundrawtransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ class RawTransactionsTest(BitcoinTestFramework):

def setup_chain(self):
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):
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,1,2)
connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)

self.is_network_split=False
self.sync_all()
Expand All @@ -31,11 +32,20 @@ def run_test(self):

self.nodes[2].generate(1)
self.sync_all()
self.nodes[0].generate(101)
self.nodes[0].generate(121)
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.0);
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);

self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
Expand Down Expand Up @@ -428,11 +438,12 @@ def run_test(self):
stop_nodes(self.nodes)
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,1,2)
connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False
self.sync_all()

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


##################################################
# 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__':
RawTransactionsTest().main()
10 changes: 10 additions & 0 deletions qa/rpc-tests/listtransactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ def run_test(self):
{"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} )

multisig = self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True)
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
self.nodes[1].generate(1)
self.sync_all()
assert(len(self.nodes[0].listtransactions("watchonly", 100, 0, False)) == 0)
check_array_result(self.nodes[0].listtransactions("watchonly", 100, 0, True),
{"category":"receive","amount":Decimal("0.1")},
{"txid":txid, "account" : "watchonly"} )

if __name__ == '__main__':
ListTransactionsTest().main()

3 changes: 3 additions & 0 deletions src/coincontrol.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class CCoinControl
CTxDestination destChange;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
bool fAllowWatchOnly;

CCoinControl()
{
Expand All @@ -24,6 +26,7 @@ class CCoinControl
{
destChange = CNoDestination();
fAllowOtherInputs = false;
fAllowWatchOnly = false;
setSelected.clear();
}

Expand Down
41 changes: 35 additions & 6 deletions src/keystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,30 @@
#include "keystore.h"

#include "key.h"
#include "pubkey.h"
#include "util.h"

#include <boost/foreach.hpp>

bool CKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
bool CKeyStore::AddKey(const CKey &key) {
return AddKeyPubKey(key, key.GetPubKey());
}

bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
{
CKey key;
if (!GetKey(address, key))
if (!GetKey(address, key)) {
WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
if (it != mapWatchKeys.end()) {
vchPubKeyOut = it->second;
return true;
}
return false;
}
vchPubKeyOut = key.GetPubKey();
return true;
}

bool CKeyStore::AddKey(const CKey &key) {
return AddKeyPubKey(key, key.GetPubKey());
}

bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
LOCK(cs_KeyStore);
Expand Down Expand Up @@ -58,17 +65,39 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
return false;
}

static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
{
//TODO: Use Solver to extract this?
CScript::const_iterator pc = dest.begin();
opcodetype opcode;
std::vector<unsigned char> vch;
if (!dest.GetOp(pc, opcode, vch) || vch.size() < 33 || vch.size() > 65)
return false;
pubKeyOut = CPubKey(vch);
if (!pubKeyOut.IsFullyValid())
return false;
if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch))
return false;
return true;
}

bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
{
LOCK(cs_KeyStore);
setWatchOnly.insert(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey))
mapWatchKeys[pubKey.GetID()] = pubKey;
return true;
}

bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest)
{
LOCK(cs_KeyStore);
setWatchOnly.erase(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey))
mapWatchKeys.erase(pubKey.GetID());
return true;
}

Expand Down
5 changes: 4 additions & 1 deletion src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class CKeyStore
virtual bool HaveKey(const CKeyID &address) const =0;
virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0;
virtual void GetKeys(std::set<CKeyID> &setAddress) const =0;
virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const =0;

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

typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CKeyID, CPubKey> WatchKeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet;

Expand All @@ -55,11 +56,13 @@ class CBasicKeyStore : public CKeyStore
{
protected:
KeyMap mapKeys;
WatchKeyMap mapWatchKeys;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;

public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
bool HaveKey(const CKeyID &address) const
{
bool result;
Expand Down
3 changes: 1 addition & 2 deletions src/qt/sendcoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,10 +754,9 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
}
else // Valid address
{
CPubKey pubkey;
CKeyID keyid;
addr.GetKeyID(keyid);
if (!model->getPubKey(keyid, pubkey)) // Unknown change address
if (!model->havePrivKey(keyid)) // Unknown change address
{
ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
}
Expand Down
4 changes: 2 additions & 2 deletions src/qt/transactiondesc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco

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

//
Expand All @@ -190,7 +190,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
if(toSelf == ISMINE_SPENDABLE)
strHTML += " (own address)";
else if(toSelf == ISMINE_WATCH_ONLY)
else if(toSelf & ISMINE_WATCH_ONLY)
strHTML += " (watch-only)";
strHTML += "<br>";
}
Expand Down
6 changes: 3 additions & 3 deletions src/qt/transactionrecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
CTxDestination address;
sub.idx = parts.size(); // sequence number
sub.credit = txout.nValue;
sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY;
sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address))
{
// Received by Bitcoin Address
Expand Down Expand Up @@ -86,15 +86,15 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{
isminetype mine = wallet->IsMine(txin);
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllFromMe > mine) fAllFromMe = mine;
}

isminetype fAllToMe = ISMINE_SPENDABLE;
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{
isminetype mine = wallet->IsMine(txout);
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllToMe > mine) fAllToMe = mine;
}

Expand Down
5 changes: 5 additions & 0 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,11 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
return wallet->GetPubKey(address, vchPubKeyOut);
}

bool WalletModel::havePrivKey(const CKeyID &address) const
{
return wallet->HaveKey(address);
}

// returns a list of COutputs from COutPoints
void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs)
{
Expand Down
1 change: 1 addition & 0 deletions src/qt/walletmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class WalletModel : public QObject
UnlockContext requestUnlock();

bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
bool havePrivKey(const CKeyID &address) const;
void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs);
bool isSpent(const COutPoint& outpoint) const;
void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const;
Expand Down
2 changes: 2 additions & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 1 },
{ "importprivkey", 2 },
{ "importaddress", 2 },
{ "importaddress", 3 },
{ "importpubkey", 2 },
{ "verifychain", 0 },
{ "verifychain", 1 },
{ "keypoolrefill", 0 },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "importprivkey", &importprivkey, true },
{ "wallet", "importwallet", &importwallet, true },
{ "wallet", "importaddress", &importaddress, true },
{ "wallet", "importpubkey", &importpubkey, true },
{ "wallet", "keypoolrefill", &keypoolrefill, true },
{ "wallet", "listaccounts", &listaccounts, false },
{ "wallet", "listaddressgroupings", &listaddressgroupings, false },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ extern UniValue clearbanned(const UniValue& params, bool fHelp);
extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp
extern UniValue importprivkey(const UniValue& params, bool fHelp);
extern UniValue importaddress(const UniValue& params, bool fHelp);
extern UniValue importpubkey(const UniValue& params, bool fHelp);
extern UniValue dumpwallet(const UniValue& params, bool fHelp);
extern UniValue importwallet(const UniValue& params, bool fHelp);

Expand Down
5 changes: 5 additions & 0 deletions src/script/standard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ CScript GetScriptForDestination(const CTxDestination& dest)
return script;
}

CScript GetScriptForRawPubKey(const CPubKey& pubKey)
{
return CScript() << std::vector<unsigned char>(pubKey.begin(), pubKey.end()) << OP_CHECKSIG;
}

CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
{
CScript script;
Expand Down
1 change: 1 addition & 0 deletions src/script/standard.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);

CScript GetScriptForDestination(const CTxDestination& dest);
CScript GetScriptForRawPubKey(const CPubKey& pubkey);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);

#endif // BITCOIN_SCRIPT_STANDARD_H
4 changes: 3 additions & 1 deletion src/wallet/crypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,16 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
{
LOCK(cs_KeyStore);
if (!IsCrypted())
return CKeyStore::GetPubKey(address, vchPubKeyOut);
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);

CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end())
{
vchPubKeyOut = (*mi).second.first;
return true;
}
// Check for watch-only pubkeys
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
}
return false;
}
Expand Down
Loading

0 comments on commit ddd8d80

Please sign in to comment.