Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement watchonly support in fundrawtransaction #6415

Merged
merged 10 commits into from Aug 14, 2015
59 changes: 55 additions & 4 deletions qa/rpc-tests/fundrawtransaction.py
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 @@ -525,5 +536,45 @@ def run_test(self):
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__':
RawTransactionsTest().main()
10 changes: 10 additions & 0 deletions qa/rpc-tests/listtransactions.py
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
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
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
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
Expand Up @@ -752,10 +752,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
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
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
Expand Up @@ -554,6 +554,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
Expand Up @@ -186,6 +186,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
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
Expand Up @@ -359,6 +359,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
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
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
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
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