diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index ce52247b2..fc2978921 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -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() @@ -31,11 +32,20 @@ class RawTransactionsTest(BitcoinTestFramework): 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() @@ -428,11 +438,12 @@ class RawTransactionsTest(BitcoinTestFramework): 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() @@ -541,5 +552,45 @@ class RawTransactionsTest(BitcoinTestFramework): 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() diff --git a/qa/rpc-tests/listtransactions.py b/qa/rpc-tests/listtransactions.py index eeae2d2fa..b30a6bc9d 100755 --- a/qa/rpc-tests/listtransactions.py +++ b/qa/rpc-tests/listtransactions.py @@ -93,6 +93,16 @@ class ListTransactionsTest(BitcoinTestFramework): {"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() diff --git a/src/coincontrol.h b/src/coincontrol.h index 3e8de83c3..bc965f9e1 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -14,6 +14,8 @@ public: 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() { @@ -24,6 +26,7 @@ public: { destChange = CNoDestination(); fAllowOtherInputs = false; + fAllowWatchOnly = false; setSelected.clear(); } diff --git a/src/keystore.cpp b/src/keystore.cpp index 3bae24b7b..cf49ba83a 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -6,23 +6,30 @@ #include "keystore.h" #include "key.h" +#include "pubkey.h" #include "util.h" #include -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); @@ -58,10 +65,29 @@ 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 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; } @@ -69,6 +95,9 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) + mapWatchKeys.erase(pubKey.GetID()); return true; } diff --git a/src/keystore.h b/src/keystore.h index 4a4b6d20a..b917bf20b 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -32,7 +32,7 @@ public: virtual bool HaveKey(const CKeyID &address) const =0; virtual bool GetKey(const CKeyID &address, CKey& keyOut) const =0; virtual void GetKeys(std::set &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; @@ -47,6 +47,7 @@ public: }; typedef std::map KeyMap; +typedef std::map WatchKeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; @@ -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; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 449a7bc5e..60a3fc128 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -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")); } diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index af78a51d0..801c6c62d 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -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 += "" + tr("From") + ": " + tr("watch-only") + "
"; // @@ -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 += "
"; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 15d13e9fc..d8623daf5 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -56,7 +56,7 @@ QList 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 @@ -86,7 +86,7 @@ QList 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; } @@ -94,7 +94,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * 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; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f58085373..5c21db8bd 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -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& vOutpoints, std::vector& vOutputs) { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index c29682e4f..a5e877d81 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -187,6 +187,7 @@ public: UnlockContext requestUnlock(); bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + bool havePrivKey(const CKeyID &address) const; void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map >& mapCoins) const; diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index b41e960e8..0c8e6d6d6 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -87,6 +87,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "lockunspent", 1 }, { "importprivkey", 2 }, { "importaddress", 2 }, + { "importaddress", 3 }, + { "importpubkey", 2 }, { "verifychain", 0 }, { "verifychain", 1 }, { "keypoolrefill", 0 }, diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 68091010f..4088f374f 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -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 }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 89d398022..3a71fd510 100644 --- a/src/rpcserver.h +++ b/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 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); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 66657127a..1d5aac7b3 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -286,6 +286,11 @@ CScript GetScriptForDestination(const CTxDestination& dest) return script; } +CScript GetScriptForRawPubKey(const CPubKey& pubKey) +{ + return CScript() << std::vector(pubKey.begin(), pubKey.end()) << OP_CHECKSIG; +} + CScript GetScriptForMultisig(int nRequired, const std::vector& keys) { CScript script; diff --git a/src/script/standard.h b/src/script/standard.h index 46ae5f9f1..9e17dac70 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -73,6 +73,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); CScript GetScriptForDestination(const CTxDestination& dest); +CScript GetScriptForRawPubKey(const CPubKey& pubkey); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 0b0fb562e..c86ad9758 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -255,7 +255,7 @@ 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()) @@ -263,6 +263,8 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co vchPubKeyOut = (*mi).second.first; return true; } + // Check for watch-only pubkeys + return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); } return false; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index dbe36a2be..8d557979c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -149,46 +149,124 @@ UniValue importprivkey(const UniValue& params, bool fHelp) 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) { if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() < 1 || params.size() > 3) + if (fHelp || params.size() < 1 || params.size() > 4) throw runtime_error( - "importaddress \"address\" ( \"label\" rescan )\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" + "importaddress \"address\" ( \"label\" rescan p2sh )\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" - "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" "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" + "If you have the full public key, you should call importpublickey instead of this.\n" "\nExamples:\n" - "\nImport an address with rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\"") + + "\nImport a script with rescan\n" + + HelpExampleCli("importaddress", "\"myscript\"") + "\nImport using a label without rescan\n" - + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + + + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") + "\nAs a JSON-RPC call\n" - + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") + + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false") ); if (fPruneMode) 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()); 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())) { std::vector data(ParseHex(params[0].get_str())); - script = CScript(data.begin(), data.end()); + ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); } else { 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 = ""; if (params.size() > 1) strLabel = params[1].get_str(); @@ -198,33 +276,28 @@ UniValue importaddress(const UniValue& params, bool fHelp) if (params.size() > 2) fRescan = params[2].get_bool(); - { - if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - - // add to address book or update label - if (address.IsValid()) - pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); - - // Don't throw error in case an address is already there - if (pwalletMain->HaveWatchOnly(script)) - return NullUniValue; + if (!IsHex(params[0].get_str())) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); + std::vector data(ParseHex(params[0].get_str())); + CPubKey pubKey(data.begin(), data.end()); + if (!pubKey.IsFullyValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - pwalletMain->MarkDirty(); + LOCK2(cs_main, pwalletMain->cs_wallet); - if (!pwalletMain->AddWatchOnly(script)) - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel); + ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); - if (fRescan) - { - pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); - pwalletMain->ReacceptWalletTransactions(); - } + if (fRescan) + { + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); + pwalletMain->ReacceptWalletTransactions(); } return NullUniValue; } + UniValue importwallet(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 198b5baf6..bd16da761 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2368,15 +2368,20 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) if (!EnsureWalletIsAvailable(fHelp)) return NullUniValue; - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) 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" "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" "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" - "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" "{\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\"") ); - RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)); // parse hex string from parameter CTransaction origTx; if (!DecodeHexTx(origTx, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + bool includeWatching = false; + if (params.size() > 1) + includeWatching = true; + CMutableTransaction tx(origTx); CAmount nFee; string strFailReason; int nChangePos = -1; - if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) + if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason, includeWatching)) throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); UniValue result(UniValue::VOBJ); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dcc298313..c3b117220 100644 --- a/src/wallet/wallet.cpp +++ b/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 CScript script; script = GetScriptForDestination(pubkey.GetID()); + if (HaveWatchOnly(script)) + RemoveWatchOnly(script); + script = GetScriptForRawPubKey(pubkey); if (HaveWatchOnly(script)) RemoveWatchOnly(script); @@ -1527,7 +1530,9 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && (!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 vecSend; @@ -1756,6 +1761,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC CCoinControl coinControl; coinControl.fAllowOtherInputs = true; + coinControl.fAllowWatchOnly = includeWatching; BOOST_FOREACH(const CTxIn& txin, tx.vin) coinControl.Select(txin.prevout); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ae007e467..bd30b67b0 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -627,7 +627,7 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() 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& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp index 5482348e3..d27b1531e 100644 --- a/src/wallet/wallet_ismine.cpp +++ b/src/wallet/wallet_ismine.cpp @@ -9,6 +9,7 @@ #include "keystore.h" #include "script/script.h" #include "script/standard.h" +#include "script/sign.h" #include @@ -40,7 +41,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) txnouttype whichType; if (!Solver(scriptPubKey, whichType, vSolutions)) { if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_ONLY; + return ISMINE_WATCH_UNSOLVABLE; return ISMINE_NO; } @@ -85,7 +86,10 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) } } - if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_ONLY; + if (keystore.HaveWatchOnly(scriptPubKey)) { + // 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; } diff --git a/src/wallet/wallet_ismine.h b/src/wallet/wallet_ismine.h index 7846565f8..9f45f76c6 100644 --- a/src/wallet/wallet_ismine.h +++ b/src/wallet/wallet_ismine.h @@ -17,8 +17,12 @@ class CScript; enum isminetype { ISMINE_NO = 0, - ISMINE_WATCH_ONLY = 1, - ISMINE_SPENDABLE = 2, + //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys + 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 }; /** used for bitflags of isminetype */