Merge pull request #6415
d042854SQUASH "Implement watchonly support in fundrawtransaction" (Matt Corallo)428a898SQUASH "Add have-pubkey distinction to ISMINE flags" (Matt Corallo)6bdb474Implement watchonly support in fundrawtransaction (Matt Corallo)f5813bdAdd logic to track pubkeys as watch-only, not just scripts (Matt Corallo)d3354c5Add have-pubkey distinction to ISMINE flags (Matt Corallo)5c17059Update importaddress help to push its use to script-only (Matt Corallo)a1d7df3Add importpubkey method to import a watch-only pubkey (Matt Corallo)907a425Add p2sh option to importaddress to import redeemScripts (Matt Corallo)983d2d9Split up importaddress into helper functions (Matt Corallo)cfc3dd3Also remove pay-2-pubkey from watch when adding a priv key (Matt Corallo)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -149,46 +149,61 @@ 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);
|
||||
|
||||
CScript script;
|
||||
|
||||
CBitcoinAddress address(params[0].get_str());
|
||||
if (address.IsValid()) {
|
||||
script = GetScriptForDestination(address.Get());
|
||||
} else if (IsHex(params[0].get_str())) {
|
||||
std::vector<unsigned char> data(ParseHex(params[0].get_str()));
|
||||
script = CScript(data.begin(), data.end());
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
||||
}
|
||||
|
||||
string strLabel = "";
|
||||
if (params.size() > 1)
|
||||
strLabel = params[1].get_str();
|
||||
@@ -198,33 +213,91 @@ UniValue importaddress(const UniValue& params, bool fHelp)
|
||||
if (params.size() > 2)
|
||||
fRescan = params[2].get_bool();
|
||||
|
||||
// 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()) {
|
||||
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<unsigned char> data(ParseHex(params[0].get_str()));
|
||||
ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
|
||||
}
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
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;
|
||||
|
||||
pwalletMain->MarkDirty();
|
||||
|
||||
if (!pwalletMain->AddWatchOnly(script))
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
|
||||
pwalletMain->ReacceptWalletTransactions();
|
||||
}
|
||||
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();
|
||||
|
||||
// Whether to perform rescan after import
|
||||
bool fRescan = true;
|
||||
if (params.size() > 2)
|
||||
fRescan = params[2].get_bool();
|
||||
|
||||
if (!IsHex(params[0].get_str()))
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
||||
std::vector<unsigned char> 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");
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
|
||||
ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
|
||||
pwalletMain->ReacceptWalletTransactions();
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
|
||||
UniValue importwallet(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<COutput>& 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<pair<const CWalletTx*
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<CRecipient>& 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);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "keystore.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "script/sign.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user