diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2016-10-20 09:04:18 +0200 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2016-10-20 09:04:32 +0200 |
commit | f2d705629b510e2a5b25c8ecac1898fed13a16a2 (patch) | |
tree | b743f1c759b0b46922f5a7ae7bce8f80d4a54ba0 /src | |
parent | c5875773561c249a079714f3b091a2577707eadf (diff) | |
parent | 215caba4ed4547d6f2a0954fa9fe1ae78f4a7c40 (diff) |
Merge #7551: Add importmulti RPC call
215caba Add consistency check to RPC call importmulti (Pedro Branco)
cb08fdb Add importmulti rpc call (Pedro Branco)
Diffstat (limited to 'src')
-rw-r--r-- | src/chain.cpp | 7 | ||||
-rw-r--r-- | src/chain.h | 3 | ||||
-rw-r--r-- | src/rpc/client.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 422 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 2 |
5 files changed, 436 insertions, 0 deletions
diff --git a/src/chain.cpp b/src/chain.cpp index 77e924e703..1e611906d1 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -61,6 +61,13 @@ const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const { return pindex; } +CBlockIndex* CChain::FindLatestBefore(int64_t nTime) const +{ + std::vector<CBlockIndex*>::const_iterator lower = std::lower_bound(vChain.begin(), vChain.end(), nTime, + [](CBlockIndex* pBlock, const int64_t& time) -> bool { return pBlock->GetBlockTime() < time; }); + return (lower == vChain.end() ? NULL : *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 e2f8c56522..46a16a3061 100644 --- a/src/chain.h +++ b/src/chain.h @@ -459,6 +459,9 @@ public: /** Find the last common block between this chain and a block index entry. */ const CBlockIndex *FindFork(const CBlockIndex *pindex) const; + + /** Find the most recent block with timestamp lower than the given. */ + CBlockIndex* FindLatestBefore(int64_t nTime) const; }; #endif // BITCOIN_CHAIN_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c14d9d6747..8370a0f43e 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -95,6 +95,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "importaddress", 2 }, { "importaddress", 3 }, { "importpubkey", 2 }, + { "importmulti", 0 }, + { "importmulti", 1 }, { "verifychain", 0 }, { "verifychain", 1 }, { "keypoolrefill", 0 }, diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 8a1bbd5684..7b16b4adfb 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -24,6 +24,7 @@ #include <univalue.h> +#include <boost/assign/list_of.hpp> #include <boost/foreach.hpp> using namespace std; @@ -637,3 +638,424 @@ UniValue dumpwallet(const JSONRPCRequest& request) file.close(); return NullUniValue; } + + +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 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 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 string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + + // Parse the output. + CScript script; + CBitcoinAddress address; + + if (!isScript) { + address = CBitcoinAddress(output); + script = GetScriptForDestination(address.Get()); + } else { + if (!IsHex(output)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); + } + + std::vector<unsigned char> 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<unsigned char> 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"); + } + + CBitcoinAddress redeemAddress = CBitcoinAddress(CScriptID(redeemScript)); + CScript redeemDestination = GetScriptForDestination(redeemAddress.Get()); + + 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 (address.IsValid()) { + pwalletMain->SetAddressBook(address.Get(), label, "receive"); + } + + // Import private keys. + if (keys.size()) { + for (size_t i = 0; i < keys.size(); i++) { + const string& privkey = keys[i].get_str(); + + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(privkey); + + if (!fGood) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + + CKey key = vchSecret.GetKey(); + + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + } + + 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 string& strPubKey = pubKeys[0].get_str(); + + if (!IsHex(strPubKey)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); + } + + std::vector<unsigned char> data(ParseHex(strPubKey)); + CPubKey pubKey(data.begin(), data.end()); + + if (!pubKey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); + } + + CBitcoinAddress pubKeyAddress = CBitcoinAddress(pubKey.GetID()); + + // Consistency check. + if (!isScript && pubKeyAddress.Get() != address.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CBitcoinAddress scriptAddress; + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + scriptAddress = CBitcoinAddress(destination); + if (scriptAddress.Get() != pubKeyAddress.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + } + } + + CScript pubKeyScript = GetScriptForDestination(pubKeyAddress.Get()); + + 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 (pubKeyAddress.IsValid()) { + pwalletMain->SetAddressBook(pubKeyAddress.Get(), 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 string& strPrivkey = keys[0].get_str(); + + // Checks. + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(strPrivkey); + + if (!fGood) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + + CKey key = vchSecret.GetKey(); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + } + + CPubKey pubKey = key.GetPubKey(); + assert(key.VerifyPubKey(pubKey)); + + CBitcoinAddress pubKeyAddress = CBitcoinAddress(pubKey.GetID()); + + // Consistency check. + if (!isScript && pubKeyAddress.Get() != address.Get()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); + } + + // Consistency check. + if (isScript) { + CBitcoinAddress scriptAddress; + CTxDestination destination; + + if (ExtractDestination(script, destination)) { + scriptAddress = CBitcoinAddress(destination); + if (scriptAddress.Get() != pubKeyAddress.Get()) { + 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)) { + return false; + } + + 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 (address.IsValid()) { + pwalletMain->SetAddressBook(address.Get(), 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) +{ + // clang-format off + if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) + throw runtime_error( + "importmulti '[<json import requests>]' '<json 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. request array (array, required) Data to be imported\n" + " [ (array of json objects)\n" + " {\n" + " \"scriptPubKey\": \"<script>\" | { \"address\":\"<address>\" }, (string / json, required) Type of scriptPubKey (string for script, json for address)\n" + " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n" + " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" + " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" + " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be be treated as not incoming payments\n" + " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n" + " \"label\": <label> , (string, optional, default: '') Label to assign to the address (aka account name, for now), only allowed with internal=false\n" + " \"timestamp\": 1454686740, (integer, optional, default now) Timestamp\n" + " }\n" + " ,...\n" + " ]\n" + "2. json options (json, optional)\n" + " {\n" + " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n" + " }\n" + "\nExamples:\n" + + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " + "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + + + "\nResponse is an array with the same size as the input that has the execution result :\n" + " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n"); + + // clang-format on + if (!EnsureWalletIsAvailable(mainRequest.fHelp)) { + return NullUniValue; + } + + RPCTypeCheck(mainRequest.params, boost::assign::list_of(UniValue::VARR)(UniValue::VOBJ)); + + const UniValue& requests = mainRequest.params[0]; + + //Default options + bool fRescan = true; + + if (mainRequest.params.size() > 1) { + const UniValue& options = mainRequest.params[1]; + + if (options.exists("rescan")) { + fRescan = options["rescan"].get_bool(); + } + } + + LOCK2(cs_main, pwalletMain->cs_wallet); + EnsureWalletIsUnlocked(); + + bool fRunScan = false; + const int64_t minimumTimestamp = 1; + int64_t nLowestTimestamp; + + if (fRescan && chainActive.Tip()) { + nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + } else { + fRescan = false; + } + + UniValue response(UniValue::VARR); + + BOOST_FOREACH (const UniValue& data, requests.getValues()) { + const UniValue result = processImport(data); + response.push_back(result); + + if (!fRescan) { + continue; + } + + // If at least one request was successful then allow rescan. + if (result["success"].get_bool()) { + fRunScan = true; + } + + // Get the lowest timestamp. + const int64_t& timestamp = data.exists("timestamp") && data["timestamp"].get_int64() > minimumTimestamp ? data["timestamp"].get_int64() : minimumTimestamp; + + if (timestamp < nLowestTimestamp) { + nLowestTimestamp = timestamp; + } + } + + if (fRescan && fRunScan && requests.size() && nLowestTimestamp <= chainActive.Tip()->GetBlockTime()) { + CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindLatestBefore(nLowestTimestamp) : chainActive.Genesis(); + + if (pindex) { + pwalletMain->ScanForWalletTransactions(pindex, true); + pwalletMain->ReacceptWalletTransactions(); + } + } + + return response; +} diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 33620aa6ff..ab7e74988a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2583,6 +2583,7 @@ extern UniValue dumpwallet(const JSONRPCRequest& request); extern UniValue importwallet(const JSONRPCRequest& request); extern UniValue importprunedfunds(const JSONRPCRequest& request); extern UniValue removeprunedfunds(const JSONRPCRequest& request); +extern UniValue importmulti(const JSONRPCRequest& request); static const CRPCCommand commands[] = { // category name actor (function) okSafeMode @@ -2607,6 +2608,7 @@ static const CRPCCommand commands[] = { "wallet", "gettransaction", &gettransaction, false }, { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false }, { "wallet", "getwalletinfo", &getwalletinfo, false }, + { "wallet", "importmulti", &importmulti, true }, { "wallet", "importprivkey", &importprivkey, true }, { "wallet", "importwallet", &importwallet, true }, { "wallet", "importaddress", &importaddress, true }, |