Auto merge of #4226 - str4d:2074-wallet-1, r=mdr0id
Bitcoin 0.12 wallet PRs 1 Cherry-picked from the following upstream PRs: - bitcoin/bitcoin#6057 - bitcoin/bitcoin#6415 Part of #2074. Closes #3700.
This commit is contained in:
commit
5e26d48a39
|
@ -18,15 +18,16 @@ 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,
|
||||
extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4)
|
||||
|
||||
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()
|
||||
|
@ -37,11 +38,20 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all()
|
||||
self.nodes[0].generate(101)
|
||||
self.nodes[0].generate(201)
|
||||
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()
|
||||
|
@ -434,11 +444,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()
|
||||
|
||||
|
@ -547,5 +558,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()
|
||||
|
|
|
@ -95,6 +95,16 @@ class ListTransactionsTest(BitcoinTestFramework):
|
|||
{"category":"receive","amount":Decimal("0.44")},
|
||||
{"txid":txid, "account" : ""} )
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
21
src/init.cpp
21
src/init.cpp
|
@ -926,16 +926,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||
nMaxConnections = nFD - MIN_CORE_FILEDESCRIPTORS;
|
||||
|
||||
// if using block pruning, then disable txindex
|
||||
// also disable the wallet (for now, until SPV support is implemented in wallet)
|
||||
if (GetArg("-prune", 0)) {
|
||||
if (GetBoolArg("-txindex", false))
|
||||
return InitError(_("Prune mode is incompatible with -txindex."));
|
||||
#ifdef ENABLE_WALLET
|
||||
if (!GetBoolArg("-disablewallet", false)) {
|
||||
if (SoftSetBoolArg("-disablewallet", true))
|
||||
LogPrintf("%s : parameter interaction: -prune -> setting -disablewallet=1\n", __func__);
|
||||
else
|
||||
return InitError(_("Can't run with a wallet in prune mode."));
|
||||
if (GetBoolArg("-rescan", false)) {
|
||||
return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again."));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -1718,6 +1714,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
|||
}
|
||||
if (chainActive.Tip() && chainActive.Tip() != pindexRescan)
|
||||
{
|
||||
// We can't rescan beyond non-pruned blocks, so stop and throw an error.
|
||||
// This might happen if a user uses a old wallet within a pruned node,
|
||||
// or if they ran -disablewallet for a longer time, then decided to re-enable.
|
||||
if (fPruneMode)
|
||||
{
|
||||
CBlockIndex *block = chainActive.Tip();
|
||||
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block)
|
||||
block = block->pprev;
|
||||
|
||||
if (pindexRescan != block)
|
||||
return InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
|
||||
}
|
||||
|
||||
uiInterface.InitMessage(_("Rescanning..."));
|
||||
LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight);
|
||||
nStart = GetTimeMillis();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
CKey key;
|
||||
if (!GetKey(address, key))
|
||||
return false;
|
||||
vchPubKeyOut = key.GetPubKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
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)) {
|
||||
WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
|
||||
if (it != mapWatchKeys.end()) {
|
||||
vchPubKeyOut = it->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
vchPubKeyOut = key.GetPubKey();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBasicKeyStore::SetHDSeed(const HDSeed& seed)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
@ -87,10 +94,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<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;
|
||||
}
|
||||
|
||||
|
@ -98,6 +124,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,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<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;
|
||||
|
@ -100,6 +100,7 @@ public:
|
|||
};
|
||||
|
||||
typedef std::map<CKeyID, CKey> KeyMap;
|
||||
typedef std::map<CKeyID, CPubKey> WatchKeyMap;
|
||||
typedef std::map<CScriptID, CScript > ScriptMap;
|
||||
typedef std::set<CScript> WatchOnlySet;
|
||||
typedef std::map<libzcash::SproutPaymentAddress, libzcash::SproutSpendingKey> SproutSpendingKeyMap;
|
||||
|
@ -119,6 +120,7 @@ class CBasicKeyStore : public CKeyStore
|
|||
protected:
|
||||
HDSeed hdSeed;
|
||||
KeyMap mapKeys;
|
||||
WatchKeyMap mapWatchKeys;
|
||||
ScriptMap mapScripts;
|
||||
WatchOnlySet setWatchOnly;
|
||||
SproutSpendingKeyMap mapSproutSpendingKeys;
|
||||
|
@ -135,6 +137,7 @@ public:
|
|||
bool GetHDSeed(HDSeed& seedOut) const;
|
||||
|
||||
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
|
||||
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const;
|
||||
bool HaveKey(const CKeyID &address) const
|
||||
{
|
||||
bool result;
|
||||
|
|
|
@ -89,6 +89,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "lockunspent", 1 },
|
||||
{ "importprivkey", 2 },
|
||||
{ "importaddress", 2 },
|
||||
{ "importaddress", 3 },
|
||||
{ "importpubkey", 2 },
|
||||
{ "verifychain", 0 },
|
||||
{ "verifychain", 1 },
|
||||
{ "keypoolrefill", 0 },
|
||||
|
|
|
@ -306,6 +306,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;
|
||||
|
|
|
@ -94,6 +94,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);
|
||||
|
||||
// insightexplorer
|
||||
|
|
|
@ -399,7 +399,7 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
|
|||
{
|
||||
LOCK(cs_KeyStore);
|
||||
if (!fUseCrypto)
|
||||
return CKeyStore::GetPubKey(address, vchPubKeyOut);
|
||||
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
|
||||
|
||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
|
||||
if (mi != mapCryptedKeys.end())
|
||||
|
@ -407,7 +407,8 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
|
|||
vchPubKeyOut = (*mi).second.first;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// Check for watch-only pubkeys
|
||||
return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
|
||||
}
|
||||
|
||||
bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
|
||||
|
|
|
@ -99,6 +99,9 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
|
|||
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
|
||||
);
|
||||
|
||||
if (fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing keys is disabled in pruned mode");
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
@ -144,42 +147,60 @@ UniValue importprivkey(const UniValue& params, bool fHelp)
|
|||
return EncodeDestination(vchAddress);
|
||||
}
|
||||
|
||||
void ImportAddress(const CTxDestination& dest, 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(CScriptID(script), strLabel);
|
||||
}
|
||||
}
|
||||
|
||||
void ImportAddress(const CTxDestination& dest, const string& strLabel)
|
||||
{
|
||||
CScript script = GetScriptForDestination(dest);
|
||||
ImportScript(script, strLabel, false);
|
||||
// add to address book or update label
|
||||
if (IsValidDestination(dest))
|
||||
pwalletMain->SetAddressBook(dest, 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")
|
||||
);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
CScript script;
|
||||
|
||||
CTxDestination dest = DecodeDestination(params[0].get_str());
|
||||
if (IsValidDestination(dest)) {
|
||||
script = GetScriptForDestination(dest);
|
||||
} 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 Zcash address or script");
|
||||
}
|
||||
if (fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing addresses is disabled in pruned mode");
|
||||
|
||||
string strLabel = "";
|
||||
if (params.size() > 1)
|
||||
|
@ -190,33 +211,92 @@ 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");
|
||||
// Whether to import a p2sh version, too
|
||||
bool fP2SH = false;
|
||||
if (params.size() > 3)
|
||||
fP2SH = params[3].get_bool();
|
||||
|
||||
// add to address book or update label
|
||||
if (IsValidDestination(dest))
|
||||
pwalletMain->SetAddressBook(dest, strLabel, "receive");
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
// 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");
|
||||
CTxDestination dest = DecodeDestination(params[0].get_str());
|
||||
if (IsValidDestination(dest)) {
|
||||
if (fP2SH) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
|
||||
}
|
||||
ImportAddress(dest, 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 Zcash 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();
|
||||
|
||||
// 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(pubKey.GetID(), strLabel);
|
||||
ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
|
||||
|
||||
if (fRescan)
|
||||
{
|
||||
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
|
||||
pwalletMain->ReacceptWalletTransactions();
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
|
||||
UniValue z_importwallet(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
|
@ -265,6 +345,9 @@ UniValue importwallet(const UniValue& params, bool fHelp)
|
|||
|
||||
UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys)
|
||||
{
|
||||
if (fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
@ -599,6 +682,9 @@ UniValue z_importkey(const UniValue& params, bool fHelp)
|
|||
+ HelpExampleRpc("z_importkey", "\"mykey\", \"no\"")
|
||||
);
|
||||
|
||||
if (fPruneMode)
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing keys is disabled in pruned mode");
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
|
|
@ -2620,15 +2620,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"
|
||||
"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"
|
||||
|
@ -2647,18 +2652,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);
|
||||
|
@ -4719,6 +4728,7 @@ UniValue z_listoperationids(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);
|
||||
extern UniValue z_exportkey(const UniValue& params, bool fHelp);
|
||||
|
@ -4755,6 +4765,7 @@ static const CRPCCommand commands[] =
|
|||
{ "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 },
|
||||
|
|
|
@ -260,6 +260,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);
|
||||
|
||||
|
@ -2983,7 +2986,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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3227,7 +3232,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;
|
||||
|
||||
|
@ -3240,6 +3245,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);
|
||||
|
||||
|
|
|
@ -1135,7 +1135,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, boost::optional<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;
|
||||
}
|
||||
|
||||
|
@ -86,7 +87,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
|
||||
SignatureData sigs;
|
||||
return ProduceSignature(DummySignatureCreator(&keystore), scriptPubKey, sigs, 0) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE;
|
||||
}
|
||||
return ISMINE_NO;
|
||||
}
|
||||
|
|
|
@ -16,8 +16,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 */
|
||||
|
|
Loading…
Reference in New Issue