Browse Source

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)
0.13
Wladimir J. van der Laan 9 years ago
parent
commit
ddd8d80c63
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 59
      qa/rpc-tests/fundrawtransaction.py
  2. 10
      qa/rpc-tests/listtransactions.py
  3. 3
      src/coincontrol.h
  4. 41
      src/keystore.cpp
  5. 5
      src/keystore.h
  6. 3
      src/qt/sendcoinsdialog.cpp
  7. 4
      src/qt/transactiondesc.cpp
  8. 6
      src/qt/transactionrecord.cpp
  9. 5
      src/qt/walletmodel.cpp
  10. 1
      src/qt/walletmodel.h
  11. 2
      src/rpcclient.cpp
  12. 1
      src/rpcserver.cpp
  13. 1
      src/rpcserver.h
  14. 5
      src/script/standard.cpp
  15. 1
      src/script/standard.h
  16. 4
      src/wallet/crypter.cpp
  17. 135
      src/wallet/rpcdump.cpp
  18. 19
      src/wallet/rpcwallet.cpp
  19. 10
      src/wallet/wallet.cpp
  20. 2
      src/wallet/wallet.h
  21. 10
      src/wallet/wallet_ismine.cpp
  22. 8
      src/wallet/wallet_ismine.h

59
qa/rpc-tests/fundrawtransaction.py

@ -13,14 +13,15 @@ class RawTransactionsTest(BitcoinTestFramework):
def setup_chain(self): def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir) 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): 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,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False self.is_network_split=False
self.sync_all() self.sync_all()
@ -31,11 +32,20 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[2].generate(1) self.nodes[2].generate(1)
self.sync_all() self.sync_all()
self.nodes[0].generate(101) self.nodes[0].generate(121)
self.sync_all() 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.5);
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0); self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0);
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0); self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0);
self.sync_all() self.sync_all()
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
@ -428,11 +438,12 @@ class RawTransactionsTest(BitcoinTestFramework):
stop_nodes(self.nodes) stop_nodes(self.nodes)
wait_bitcoinds() 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,0,1)
connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2) connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)
self.is_network_split=False self.is_network_split=False
self.sync_all() self.sync_all()
@ -541,5 +552,45 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_equal(len(dec_tx['vout']), 2) # one change output added 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__': if __name__ == '__main__':
RawTransactionsTest().main() RawTransactionsTest().main()

10
qa/rpc-tests/listtransactions.py

@ -93,6 +93,16 @@ class ListTransactionsTest(BitcoinTestFramework):
{"category":"receive","amount":Decimal("0.44")}, {"category":"receive","amount":Decimal("0.44")},
{"txid":txid, "account" : "toself"} ) {"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__': if __name__ == '__main__':
ListTransactionsTest().main() ListTransactionsTest().main()

3
src/coincontrol.h

@ -14,6 +14,8 @@ public:
CTxDestination destChange; CTxDestination destChange;
//! If false, allows unselected inputs, but requires all selected inputs be used //! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs; bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
bool fAllowWatchOnly;
CCoinControl() CCoinControl()
{ {
@ -24,6 +26,7 @@ public:
{ {
destChange = CNoDestination(); destChange = CNoDestination();
fAllowOtherInputs = false; fAllowOtherInputs = false;
fAllowWatchOnly = false;
setSelected.clear(); setSelected.clear();
} }

41
src/keystore.cpp

@ -6,23 +6,30 @@
#include "keystore.h" #include "keystore.h"
#include "key.h" #include "key.h"
#include "pubkey.h"
#include "util.h" #include "util.h"
#include <boost/foreach.hpp> #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; 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; return false;
}
vchPubKeyOut = key.GetPubKey(); vchPubKeyOut = key.GetPubKey();
return true; return true;
} }
bool CKeyStore::AddKey(const CKey &key) {
return AddKeyPubKey(key, key.GetPubKey());
}
bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
@ -58,10 +65,29 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
return false; 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) bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
setWatchOnly.insert(dest); setWatchOnly.insert(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey))
mapWatchKeys[pubKey.GetID()] = pubKey;
return true; return true;
} }
@ -69,6 +95,9 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
setWatchOnly.erase(dest); setWatchOnly.erase(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey))
mapWatchKeys.erase(pubKey.GetID());
return true; return true;
} }

5
src/keystore.h

@ -32,7 +32,7 @@ public:
virtual bool HaveKey(const CKeyID &address) const =0; virtual bool HaveKey(const CKeyID &address) const =0;
virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0; virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0;
virtual void GetKeys(std::set<CKeyID> &setAddress) 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 //! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki
virtual bool AddCScript(const CScript& redeemScript) =0; virtual bool AddCScript(const CScript& redeemScript) =0;
@ -47,6 +47,7 @@ public:
}; };
typedef std::map<CKeyID, CKey> KeyMap; typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CKeyID, CPubKey> WatchKeyMap;
typedef std::map<CScriptID, CScript > ScriptMap; typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet; typedef std::set<CScript> WatchOnlySet;
@ -55,11 +56,13 @@ class CBasicKeyStore : public CKeyStore
{ {
protected: protected:
KeyMap mapKeys; KeyMap mapKeys;
WatchKeyMap mapWatchKeys;
ScriptMap mapScripts; ScriptMap mapScripts;
WatchOnlySet setWatchOnly; WatchOnlySet setWatchOnly;
public: public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
bool HaveKey(const CKeyID &address) const bool HaveKey(const CKeyID &address) const
{ {
bool result; bool result;

3
src/qt/sendcoinsdialog.cpp

@ -754,10 +754,9 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text)
} }
else // Valid address else // Valid address
{ {
CPubKey pubkey;
CKeyID keyid; CKeyID keyid;
addr.GetKeyID(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")); ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
} }

4
src/qt/transactiondesc.cpp

@ -165,7 +165,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
if (fAllFromMe) if (fAllFromMe)
{ {
if(fAllFromMe == ISMINE_WATCH_ONLY) if(fAllFromMe & ISMINE_WATCH_ONLY)
strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>"; strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
// //
@ -190,7 +190,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
if(toSelf == ISMINE_SPENDABLE) if(toSelf == ISMINE_SPENDABLE)
strHTML += " (own address)"; strHTML += " (own address)";
else if(toSelf == ISMINE_WATCH_ONLY) else if(toSelf & ISMINE_WATCH_ONLY)
strHTML += " (watch-only)"; strHTML += " (watch-only)";
strHTML += "<br>"; strHTML += "<br>";
} }

6
src/qt/transactionrecord.cpp

@ -56,7 +56,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
CTxDestination address; CTxDestination address;
sub.idx = parts.size(); // sequence number sub.idx = parts.size(); // sequence number
sub.credit = txout.nValue; sub.credit = txout.nValue;
sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY; sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY;
if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address))
{ {
// Received by Bitcoin Address // Received by Bitcoin Address
@ -86,7 +86,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
BOOST_FOREACH(const CTxIn& txin, wtx.vin) BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{ {
isminetype mine = wallet->IsMine(txin); isminetype mine = wallet->IsMine(txin);
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllFromMe > mine) fAllFromMe = mine; if(fAllFromMe > mine) fAllFromMe = mine;
} }
@ -94,7 +94,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *
BOOST_FOREACH(const CTxOut& txout, wtx.vout) BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{ {
isminetype mine = wallet->IsMine(txout); isminetype mine = wallet->IsMine(txout);
if(mine == ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true;
if(fAllToMe > mine) fAllToMe = mine; if(fAllToMe > mine) fAllToMe = mine;
} }

5
src/qt/walletmodel.cpp

@ -556,6 +556,11 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
return wallet->GetPubKey(address, vchPubKeyOut); return wallet->GetPubKey(address, vchPubKeyOut);
} }
bool WalletModel::havePrivKey(const CKeyID &address) const
{
return wallet->HaveKey(address);
}
// returns a list of COutputs from COutPoints // returns a list of COutputs from COutPoints
void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs) void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs)
{ {

1
src/qt/walletmodel.h

@ -187,6 +187,7 @@ public:
UnlockContext requestUnlock(); UnlockContext requestUnlock();
bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; 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); void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs);
bool isSpent(const COutPoint& outpoint) const; bool isSpent(const COutPoint& outpoint) const;
void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const; void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const;

2
src/rpcclient.cpp

@ -87,6 +87,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 1 }, { "lockunspent", 1 },
{ "importprivkey", 2 }, { "importprivkey", 2 },
{ "importaddress", 2 }, { "importaddress", 2 },
{ "importaddress", 3 },
{ "importpubkey", 2 },
{ "verifychain", 0 }, { "verifychain", 0 },
{ "verifychain", 1 }, { "verifychain", 1 },
{ "keypoolrefill", 0 }, { "keypoolrefill", 0 },

1
src/rpcserver.cpp

@ -363,6 +363,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "importprivkey", &importprivkey, true }, { "wallet", "importprivkey", &importprivkey, true },
{ "wallet", "importwallet", &importwallet, true }, { "wallet", "importwallet", &importwallet, true },
{ "wallet", "importaddress", &importaddress, true }, { "wallet", "importaddress", &importaddress, true },
{ "wallet", "importpubkey", &importpubkey, true },
{ "wallet", "keypoolrefill", &keypoolrefill, true }, { "wallet", "keypoolrefill", &keypoolrefill, true },
{ "wallet", "listaccounts", &listaccounts, false }, { "wallet", "listaccounts", &listaccounts, false },
{ "wallet", "listaddressgroupings", &listaddressgroupings, false }, { "wallet", "listaddressgroupings", &listaddressgroupings, false },

1
src/rpcserver.h

@ -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 dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp
extern UniValue importprivkey(const UniValue& params, bool fHelp); extern UniValue importprivkey(const UniValue& params, bool fHelp);
extern UniValue importaddress(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 dumpwallet(const UniValue& params, bool fHelp);
extern UniValue importwallet(const UniValue& params, bool fHelp); extern UniValue importwallet(const UniValue& params, bool fHelp);

5
src/script/standard.cpp

@ -286,6 +286,11 @@ CScript GetScriptForDestination(const CTxDestination& dest)
return script; 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 GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
{ {
CScript script; CScript script;

1
src/script/standard.h

@ -73,6 +73,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForDestination(const CTxDestination& dest);
CScript GetScriptForRawPubKey(const CPubKey& pubkey);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
#endif // BITCOIN_SCRIPT_STANDARD_H #endif // BITCOIN_SCRIPT_STANDARD_H

4
src/wallet/crypter.cpp

@ -255,7 +255,7 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
if (!IsCrypted()) if (!IsCrypted())
return CKeyStore::GetPubKey(address, vchPubKeyOut); return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end()) if (mi != mapCryptedKeys.end())
@ -263,6 +263,8 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
vchPubKeyOut = (*mi).second.first; vchPubKeyOut = (*mi).second.first;
return true; return true;
} }
// Check for watch-only pubkeys
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
} }
return false; return false;
} }

135
src/wallet/rpcdump.cpp

@ -149,46 +149,124 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
return NullUniValue; return NullUniValue;
} }
void ImportAddress(const CBitcoinAddress& address, const string& strLabel);
void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript)
{
if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
if (isRedeemScript) {
if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel);
}
}
void ImportAddress(const CBitcoinAddress& address, const string& strLabel)
{
CScript script = GetScriptForDestination(address.Get());
ImportScript(script, strLabel, false);
// add to address book or update label
if (address.IsValid())
pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
}
UniValue importaddress(const UniValue& params, bool fHelp) UniValue importaddress(const UniValue& params, bool fHelp)
{ {
if (!EnsureWalletIsAvailable(fHelp)) if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue; return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 3) if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error( throw runtime_error(
"importaddress \"address\" ( \"label\" rescan )\n" "importaddress \"address\" ( \"label\" rescan p2sh )\n"
"\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n" "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"address\" (string, required) The address\n" "1. \"script\" (string, required) The hex-encoded script (or address)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n" "2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
"\nNote: This call can take minutes to complete if rescan is true.\n" "\nNote: This call can take minutes to complete if rescan is true.\n"
"If you have the full public key, you should call importpublickey instead of this.\n"
"\nExamples:\n" "\nExamples:\n"
"\nImport an address with rescan\n" "\nImport a script with rescan\n"
+ HelpExampleCli("importaddress", "\"myaddress\"") + + HelpExampleCli("importaddress", "\"myscript\"") +
"\nImport using a label without rescan\n" "\nImport using a label without rescan\n"
+ HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
"\nAs a JSON-RPC call\n" "\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")
); );
if (fPruneMode) if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing addresses is disabled in pruned mode"); throw JSONRPCError(RPC_WALLET_ERROR, "Importing addresses is disabled in pruned mode");
LOCK2(cs_main, pwalletMain->cs_wallet); string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();
// Whether to perform rescan after import
bool fRescan = true;
if (params.size() > 2)
fRescan = params[2].get_bool();
CScript script; // Whether to import a p2sh version, too
bool fP2SH = false;
if (params.size() > 3)
fP2SH = params[3].get_bool();
LOCK2(cs_main, pwalletMain->cs_wallet);
CBitcoinAddress address(params[0].get_str()); CBitcoinAddress address(params[0].get_str());
if (address.IsValid()) { if (address.IsValid()) {
script = GetScriptForDestination(address.Get()); if (fP2SH)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
ImportAddress(address, strLabel);
} else if (IsHex(params[0].get_str())) { } else if (IsHex(params[0].get_str())) {
std::vector<unsigned char> data(ParseHex(params[0].get_str())); std::vector<unsigned char> data(ParseHex(params[0].get_str()));
script = CScript(data.begin(), data.end()); ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH);
} else { } else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
} }
if (fRescan)
{
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
pwalletMain->ReacceptWalletTransactions();
}
return NullUniValue;
}
UniValue importpubkey(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error(
"importpubkey \"pubkey\" ( \"label\" rescan )\n"
"\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
"\nArguments:\n"
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
"\nImport using a label without rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
);
if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing public keys is disabled in pruned mode");
string strLabel = ""; string strLabel = "";
if (params.size() > 1) if (params.size() > 1)
strLabel = params[1].get_str(); strLabel = params[1].get_str();
@ -198,33 +276,28 @@ UniValue importaddress(const UniValue& params, bool fHelp)
if (params.size() > 2) if (params.size() > 2)
fRescan = params[2].get_bool(); fRescan = params[2].get_bool();
{ if (!IsHex(params[0].get_str()))
if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); std::vector<unsigned char> data(ParseHex(params[0].get_str()));
CPubKey pubKey(data.begin(), data.end());
// add to address book or update label if (!pubKey.IsFullyValid())
if (address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
// Don't throw error in case an address is already there
if (pwalletMain->HaveWatchOnly(script))
return NullUniValue;
pwalletMain->MarkDirty(); LOCK2(cs_main, pwalletMain->cs_wallet);
if (!pwalletMain->AddWatchOnly(script)) ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
if (fRescan) if (fRescan)
{ {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
pwalletMain->ReacceptWalletTransactions(); pwalletMain->ReacceptWalletTransactions();
}
} }
return NullUniValue; return NullUniValue;
} }
UniValue importwallet(const UniValue& params, bool fHelp) UniValue importwallet(const UniValue& params, bool fHelp)
{ {
if (!EnsureWalletIsAvailable(fHelp)) if (!EnsureWalletIsAvailable(fHelp))

19
src/wallet/rpcwallet.cpp

@ -2368,15 +2368,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
if (!EnsureWalletIsAvailable(fHelp)) if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue; return NullUniValue;
if (fHelp || params.size() != 1) if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error( throw runtime_error(
"fundrawtransaction \"hexstring\"\n" "fundrawtransaction \"hexstring\" includeWatching\n"
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
"This will not modify existing inputs, and will add one change output to the outputs.\n" "This will not modify existing inputs, and will add one change output to the outputs.\n"
"Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n"
"The inputs added will not be signed, use signrawtransaction for that.\n" "The inputs added will not be signed, use signrawtransaction for that.\n"
"Note that all existing inputs must have their previous output transaction be in the wallet.\n"
"Note that all inputs selected must be of standard form and P2SH scripts must be"
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n" "1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
"2. includeWatching (boolean, optional, default false) Also select inputs which are watch only\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
@ -2395,18 +2400,22 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
+ HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")
); );
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
// parse hex string from parameter // parse hex string from parameter
CTransaction origTx; CTransaction origTx;
if (!DecodeHexTx(origTx, params[0].get_str())) if (!DecodeHexTx(origTx, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
bool includeWatching = false;
if (params.size() > 1)
includeWatching = true;
CMutableTransaction tx(origTx); CMutableTransaction tx(origTx);
CAmount nFee; CAmount nFee;
string strFailReason; string strFailReason;
int nChangePos = -1; int nChangePos = -1;
if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching))
throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason);
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);

10
src/wallet/wallet.cpp

@ -112,6 +112,9 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
// check if we need to remove from watch-only // check if we need to remove from watch-only
CScript script; CScript script;
script = GetScriptForDestination(pubkey.GetID()); script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script))
RemoveWatchOnly(script);
script = GetScriptForRawPubKey(pubkey);
if (HaveWatchOnly(script)) if (HaveWatchOnly(script))
RemoveWatchOnly(script); RemoveWatchOnly(script);
@ -1527,7 +1530,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i)))
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); vCoins.push_back(COutput(pcoin, i, nDepth,
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO)));
} }
} }
} }
@ -1743,7 +1748,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
return res; return res;
} }
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason) bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching)
{ {
vector<CRecipient> vecSend; vector<CRecipient> vecSend;
@ -1756,6 +1761,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
CCoinControl coinControl; CCoinControl coinControl;
coinControl.fAllowOtherInputs = true; coinControl.fAllowOtherInputs = true;
coinControl.fAllowWatchOnly = includeWatching;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
coinControl.Select(txin.prevout); coinControl.Select(txin.prevout);

2
src/wallet/wallet.h

@ -627,7 +627,7 @@ public:
CAmount GetWatchOnlyBalance() const; CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const;
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason); bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);

10
src/wallet/wallet_ismine.cpp

@ -9,6 +9,7 @@
#include "keystore.h" #include "keystore.h"
#include "script/script.h" #include "script/script.h"
#include "script/standard.h" #include "script/standard.h"
#include "script/sign.h"
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
@ -40,7 +41,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
txnouttype whichType; txnouttype whichType;
if (!Solver(scriptPubKey, whichType, vSolutions)) { if (!Solver(scriptPubKey, whichType, vSolutions)) {
if (keystore.HaveWatchOnly(scriptPubKey)) if (keystore.HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY; return ISMINE_WATCH_UNSOLVABLE;
return ISMINE_NO; return ISMINE_NO;
} }
@ -85,7 +86,10 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
} }
} }
if (keystore.HaveWatchOnly(scriptPubKey)) if (keystore.HaveWatchOnly(scriptPubKey)) {
return ISMINE_WATCH_ONLY; // TODO: This could be optimized some by doing some work after the above solver
CScript scriptSig;
return ProduceSignature(DummySignatureCreator(&keystore), scriptPubKey, scriptSig) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE;
}
return ISMINE_NO; return ISMINE_NO;
} }

8
src/wallet/wallet_ismine.h

@ -17,8 +17,12 @@ class CScript;
enum isminetype enum isminetype
{ {
ISMINE_NO = 0, ISMINE_NO = 0,
ISMINE_WATCH_ONLY = 1, //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys
ISMINE_SPENDABLE = 2, ISMINE_WATCH_UNSOLVABLE = 1,
//! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys
ISMINE_WATCH_SOLVABLE = 2,
ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE,
ISMINE_SPENDABLE = 4,
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE
}; };
/** used for bitflags of isminetype */ /** used for bitflags of isminetype */

Loading…
Cancel
Save