From d1bb97346d2e70153a672b42259dd66ed26453f4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 29 Mar 2021 23:54:11 +0200 Subject: [PATCH 1/5] [RPC] Add importmulti call --- src/chain.cpp | 7 + src/chain.h | 8 + src/rpc/client.cpp | 2 + src/validation.cpp | 2 + src/wallet/rpcdump.cpp | 392 +++++++++++++++++++++++++++++++++++++++ src/wallet/rpcwallet.cpp | 2 + 6 files changed, 413 insertions(+) diff --git a/src/chain.cpp b/src/chain.cpp index 60ab641844c6..22cc125868de 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -64,6 +64,13 @@ const CBlockIndex* CChain::FindFork(const CBlockIndex* pindex) const return pindex; } +CBlockIndex* CChain::FindEarliestAtLeast(int64_t nTime) const +{ + std::vector::const_iterator lower = std::lower_bound(vChain.begin(), vChain.end(), nTime, + [](CBlockIndex* pBlock, const int64_t& time) -> bool { return pBlock->GetBlockTimeMax() < time; }); + return (lower == vChain.end() ? nullptr : *lower); +} + /** Turn the lowest '1' bit in the binary representation of a number into a '0'. */ int static inline InvertLowestOne(int n) { return n & (n - 1); } diff --git a/src/chain.h b/src/chain.h index 79b7b3617949..eff3d71635e5 100644 --- a/src/chain.h +++ b/src/chain.h @@ -239,6 +239,9 @@ class CBlockIndex //! (memory only) Sequential id assigned to distinguish order in which blocks are received. uint32_t nSequenceId{0}; + //! (memory only) Maximum nTime in the chain upto and including this block. + unsigned int nTimeMax{0}; + CBlockIndex() {} CBlockIndex(const CBlock& block); @@ -249,6 +252,8 @@ class CBlockIndex CBlockHeader GetBlockHeader() const; uint256 GetBlockHash() const { return *phashBlock; } int64_t GetBlockTime() const { return (int64_t)nTime; } + int64_t GetBlockTimeMax() const { return (int64_t)nTimeMax; } + int64_t GetMedianTimePast() const; int64_t MaxFutureBlockTime() const; @@ -595,6 +600,9 @@ class CChain /** Find the last common block between this chain and a block index entry. */ const CBlockIndex* FindFork(const CBlockIndex* pindex) const; + + /** Find the earliest block with timestamp equal or greater than the given. */ + CBlockIndex* FindEarliestAtLeast(int64_t nTime) const; }; #endif // BITCOIN_CHAIN_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 03f6ee06c1ce..45f12d02ffc3 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -116,6 +116,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { { "importaddress", 2 }, { "importaddress", 3 }, { "importpubkey", 2 }, + { "importmulti", 0 }, + { "importmulti", 1 }, { "exportsaplingkey", 1 }, { "importsaplingkey", 2 }, { "importsaplingviewingkey", 2 }, diff --git a/src/validation.cpp b/src/validation.cpp index 59ff2cc0d127..c3e81d09bf73 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2426,6 +2426,7 @@ CBlockIndex* AddToBlockIndex(const CBlock& block) pindexNew->SetNewStakeModifier(block.vtx[1]->vin[0].prevout.hash); } } + pindexNew->nTimeMax = (pindexNew->pprev ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime) : pindexNew->nTime); pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew); pindexNew->RaiseValidity(BLOCK_VALID_TREE); if (pindexBestHeader == NULL || pindexBestHeader->nChainWork < pindexNew->nChainWork) @@ -3436,6 +3437,7 @@ bool static LoadBlockIndexDB(std::string& strError) CBlockIndex* pindex = item.second; pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex); + pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime); if (pindex->nStatus & BLOCK_HAVE_DATA) { if (pindex->pprev) { if (pindex->pprev->nChainTx) { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index a8a063b3aca4..b7eb71808e30 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -612,6 +612,398 @@ UniValue dumpwallet(const JSONRPCRequest& request) return reply; } +UniValue processImport(const UniValue& data) +{ + try { + bool success = false; + + // Required fields. + const UniValue& scriptPubKey = data["scriptPubKey"]; + + // Should have script or JSON with "address". + if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey"); + } + + // Optional fields. + const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; + const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); + const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); + const bool& internal = data.exists("internal") ? data["internal"].get_bool() : false; + const bool& watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; + const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : ""; + const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > 1 ? data["timestamp"].get_int64() : 1; + + bool isScript = scriptPubKey.getType() == UniValue::VSTR; + bool isP2SH = strRedeemScript.length() > 0; + const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + + // Parse the output. + CScript script; + CTxDestination dest; + + if (!isScript) { + dest = DecodeDestination(output); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + script = GetScriptForDestination(dest); + } else { + if (!IsHex(output)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); + } + std::vector vData(ParseHex(output)); + script = CScript(vData.begin(), vData.end()); + } + + // Watchonly and private keys + if (watchOnly && keys.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); + } + + // Internal + Label + if (internal && data.exists("label")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); + } + + // Not having Internal + Script + if (!internal && isScript) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey"); + } + + // Keys / PubKeys size check. + if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey + throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); + } + + // Invalid P2SH redeemScript + if (isP2SH && !IsHex(strRedeemScript)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); + } + + // Process. // + + // P2SH + if (isP2SH) { + // Import redeem script. + std::vector vData(ParseHex(strRedeemScript)); + CScript redeemScript = CScript(vData.begin(), vData.end()); + + // Invalid P2SH address + if (!script.IsPayToScriptHash()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); + } + + pwalletMain->MarkDirty(); + + if (!pwalletMain->HaveWatchOnly(redeemScript) && !pwalletMain->AddWatchOnly(redeemScript)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + if (!pwalletMain->HaveCScript(redeemScript) && !pwalletMain->AddCScript(redeemScript)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); + } + + CTxDestination redeem_dest = CScriptID(redeemScript); + CScript redeemDestination = GetScriptForDestination(redeem_dest); + + if (::IsMine(*pwalletMain, redeemDestination) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwalletMain->MarkDirty(); + + if (!pwalletMain->HaveWatchOnly(redeemDestination) && !pwalletMain->AddWatchOnly(redeemDestination)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + // add to address book or update label + if (IsValidDestination(dest)) { + pwalletMain->SetAddressBook(dest, label, "receive"); + } + + // Import private keys. + if (keys.size()) { + for (size_t i = 0; i < keys.size(); i++) { + const std::string& privkey = keys[i].get_str(); + CKey key = DecodeSecret(privkey); + + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + + CKeyID vchAddress = pubkey.GetID(); + pwalletMain->MarkDirty(); + pwalletMain->SetAddressBook(vchAddress, label, "receive"); + + if (pwalletMain->HaveKey(vchAddress)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); + } + + pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; + + if (!pwalletMain->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + + if (timestamp < pwalletMain->nTimeFirstKey) { + pwalletMain->nTimeFirstKey = timestamp; + } + } + } + + success = true; + } else { + // Import public keys. + if (pubKeys.size() && keys.size() == 0) { + const std::string& strPubKey = pubKeys[0].get_str(); + + if (!IsHex(strPubKey)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); + } + + std::vector vData(ParseHex(strPubKey)); + CPubKey pubKey(vData.begin(), vData.end()); + + if (!pubKey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); + } + + CTxDestination pubkey_dest = pubKey.GetID(); + + // Consistency check. + if (!isScript && !(pubkey_dest == dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CTxDestination destination; + if (ExtractDestination(script, destination) && !(destination == pubkey_dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + + CScript pubKeyScript = GetScriptForDestination(pubkey_dest); + + if (::IsMine(*pwalletMain, pubKeyScript) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwalletMain->MarkDirty(); + + if (!pwalletMain->HaveWatchOnly(pubKeyScript) && !pwalletMain->AddWatchOnly(pubKeyScript)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + // add to address book or update label + if (IsValidDestination(pubkey_dest)) { + pwalletMain->SetAddressBook(pubkey_dest, label, "receive"); + } + + // TODO Is this necessary? + CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); + + if (::IsMine(*pwalletMain, scriptRawPubKey) == ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwalletMain->MarkDirty(); + + if (!pwalletMain->HaveWatchOnly(scriptRawPubKey) && !pwalletMain->AddWatchOnly(scriptRawPubKey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + + success = true; + } + + // Import private keys. + if (keys.size()) { + const std::string& strPrivkey = keys[0].get_str(); + CKey key = DecodeSecret(strPrivkey); + + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + + CPubKey pubKey = key.GetPubKey(); + assert(key.VerifyPubKey(pubKey)); + + CTxDestination pubkey_dest = pubKey.GetID(); + + // Consistency check. + if (!isScript && !(pubkey_dest == dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CTxDestination destination; + if (ExtractDestination(script, destination) && !(destination == pubkey_dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + + CKeyID vchAddress = pubKey.GetID(); + pwalletMain->MarkDirty(); + pwalletMain->SetAddressBook(vchAddress, label, "receive"); + + if (pwalletMain->HaveKey(vchAddress)) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } + + pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = timestamp; + + if (!pwalletMain->AddKeyPubKey(key, pubKey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + + if (timestamp < pwalletMain->nTimeFirstKey) { + pwalletMain->nTimeFirstKey = timestamp; + } + + success = true; + } + + // Import scriptPubKey only. + if (pubKeys.size() == 0 && keys.size() == 0) { + if (::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 (scriptPubKey.getType() == UniValue::VOBJ) { + // add to address book or update label + if (IsValidDestination(dest)) { + pwalletMain->SetAddressBook(dest, label, "receive"); + } + } + + success = true; + } + } + + UniValue result = UniValue(UniValue::VOBJ); + result.pushKV("success", UniValue(success)); + return result; + } catch (const UniValue& e) { + UniValue result = UniValue(UniValue::VOBJ); + result.pushKV("success", UniValue(false)); + result.pushKV("error", e); + return result; + } catch (...) { + UniValue result = UniValue(UniValue::VOBJ); + result.pushKV("success", UniValue(false)); + result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); + return result; + } +} + +UniValue importmulti(const JSONRPCRequest& mainRequest) +{ + if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) + throw std::runtime_error( + "importmulti \"requests\" \"options\"\n\n" + "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n" + "Arguments:\n" + "1. requests (array, required) Data to be imported\n" + " [ (array of json objects)\n" + " {\n" + " \"scriptPubKey\": \"