diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 691 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 3 | ||||
-rw-r--r-- | src/rpc/client.cpp | 22 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 43 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 75 | ||||
-rw-r--r-- | src/rpc/net.cpp | 26 | ||||
-rw-r--r-- | src/rpc/protocol.h | 4 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 771 | ||||
-rw-r--r-- | src/rpc/rawtransaction.h | 3 | ||||
-rw-r--r-- | src/rpc/safemode.cpp | 14 | ||||
-rw-r--r-- | src/rpc/safemode.h | 12 | ||||
-rw-r--r-- | src/rpc/server.cpp | 12 |
12 files changed, 1400 insertions, 276 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 03c097197f..012e3e3ac1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -6,6 +6,7 @@ #include <rpc/blockchain.h> #include <amount.h> +#include <base58.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> @@ -13,6 +14,8 @@ #include <consensus/validation.h> #include <validation.h> #include <core_io.h> +#include <index/txindex.h> +#include <key_io.h> #include <policy/feerate.h> #include <policy/policy.h> #include <primitives/transaction.h> @@ -27,10 +30,12 @@ #include <validationinterface.h> #include <warnings.h> +#include <assert.h> #include <stdint.h> #include <univalue.h> +#include <boost/algorithm/string.hpp> #include <boost/thread/thread.hpp> // boost::thread::interrupt #include <memory> @@ -47,17 +52,13 @@ static std::mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock; -/* Calculate the difficulty for a given block index, - * or the block index of the given chain. +/* Calculate the difficulty for a given block index. */ -double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex) +double GetDifficulty(const CBlockIndex* blockindex) { if (blockindex == nullptr) { - if (chain.Tip() == nullptr) - return 1.0; - else - blockindex = chain.Tip(); + return 1.0; } int nShift = (blockindex->nBits >> 24) & 0xff; @@ -78,11 +79,6 @@ double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex) return dDiff; } -double GetDifficulty(const CBlockIndex* blockindex) -{ - return GetDifficulty(chainActive, blockindex); -} - UniValue blockheaderToJSON(const CBlockIndex* blockindex) { AssertLockHeld(cs_main); @@ -103,6 +99,7 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) result.pushKV("bits", strprintf("%08x", blockindex->nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + result.pushKV("nTx", (uint64_t)blockindex->nTx); if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); @@ -148,6 +145,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + result.pushKV("nTx", (uint64_t)blockindex->nTx); if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); @@ -157,7 +155,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx return result; } -UniValue getblockcount(const JSONRPCRequest& request) +static UniValue getblockcount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -174,7 +172,7 @@ UniValue getblockcount(const JSONRPCRequest& request) return chainActive.Height(); } -UniValue getbestblockhash(const JSONRPCRequest& request) +static UniValue getbestblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -201,7 +199,7 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex) cond_blockchange.notify_all(); } -UniValue waitfornewblock(const JSONRPCRequest& request) +static UniValue waitfornewblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -239,7 +237,7 @@ UniValue waitfornewblock(const JSONRPCRequest& request) return ret; } -UniValue waitforblock(const JSONRPCRequest& request) +static UniValue waitforblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -281,7 +279,7 @@ UniValue waitforblock(const JSONRPCRequest& request) return ret; } -UniValue waitforblockheight(const JSONRPCRequest& request) +static UniValue waitforblockheight(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -323,7 +321,7 @@ UniValue waitforblockheight(const JSONRPCRequest& request) return ret; } -UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) +static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( @@ -338,7 +336,7 @@ UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) return NullUniValue; } -UniValue getdifficulty(const JSONRPCRequest& request) +static UniValue getdifficulty(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -352,10 +350,10 @@ UniValue getdifficulty(const JSONRPCRequest& request) ); LOCK(cs_main); - return GetDifficulty(); + return GetDifficulty(chainActive.Tip()); } -std::string EntryDescriptionString() +static std::string EntryDescriptionString() { return " \"size\" : n, (numeric) virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.\n" " \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)\n" @@ -373,7 +371,7 @@ std::string EntryDescriptionString() " \"base\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + "\n" " \"modified\" : n, (numeric) transaction fee with fee deltas used for mining priority in " + CURRENCY_UNIT + "\n" " \"ancestor\" : n, (numeric) modified fees (see above) of in-mempool ancestors (including this one) in " + CURRENCY_UNIT + "\n" - " \"descendent\" : n, (numeric) number of in-mempool ancestor transactions (including this one) in " + CURRENCY_UNIT + "\n" + " \"descendant\" : n, (numeric) modified fees (see above) of in-mempool descendants (including this one) in " + CURRENCY_UNIT + "\n" " }\n" " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" " \"transactionid\", (string) parent transaction id\n" @@ -383,7 +381,7 @@ std::string EntryDescriptionString() " ... ]\n"; } -void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) +static void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs) { AssertLockHeld(mempool.cs); @@ -460,7 +458,7 @@ UniValue mempoolToJSON(bool fVerbose) } } -UniValue getrawmempool(const JSONRPCRequest& request) +static UniValue getrawmempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -492,7 +490,7 @@ UniValue getrawmempool(const JSONRPCRequest& request) return mempoolToJSON(fVerbose); } -UniValue getmempoolancestors(const JSONRPCRequest& request) +static UniValue getmempoolancestors(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -556,7 +554,7 @@ UniValue getmempoolancestors(const JSONRPCRequest& request) } } -UniValue getmempooldescendants(const JSONRPCRequest& request) +static UniValue getmempooldescendants(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -620,7 +618,7 @@ UniValue getmempooldescendants(const JSONRPCRequest& request) } } -UniValue getmempoolentry(const JSONRPCRequest& request) +static UniValue getmempoolentry(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( @@ -653,7 +651,7 @@ UniValue getmempoolentry(const JSONRPCRequest& request) return info; } -UniValue getblockhash(const JSONRPCRequest& request) +static UniValue getblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -678,7 +676,7 @@ UniValue getblockhash(const JSONRPCRequest& request) return pblockindex->GetBlockHash().GetHex(); } -UniValue getblockheader(const JSONRPCRequest& request) +static UniValue getblockheader(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -702,6 +700,7 @@ UniValue getblockheader(const JSONRPCRequest& request) " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"0000...1f3\" (string) Expected number of hashes required to produce the current chain (in hex)\n" + " \"nTx\" : n, (numeric) The number of transactions in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\", (string) The hash of the next block\n" "}\n" @@ -737,7 +736,26 @@ UniValue getblockheader(const JSONRPCRequest& request) return blockheaderToJSON(pblockindex); } -UniValue getblock(const JSONRPCRequest& request) +static CBlock GetBlockChecked(const CBlockIndex* pblockindex) +{ + CBlock block; + if (IsBlockPruned(pblockindex)) { + throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); + } + + if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { + // Block not found on disk. This could be because we have the block + // header in our index but don't have the block (for example if a + // non-whitelisted node sends us an unrequested long chain of valid + // blocks, we add the headers to our index, but don't accept the + // block). + throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); + } + + return block; +} + +static UniValue getblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -771,6 +789,7 @@ UniValue getblock(const JSONRPCRequest& request) " \"bits\" : \"1d00ffff\", (string) The bits\n" " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"chainwork\" : \"xxxx\", (string) Expected number of hashes required to produce the chain up to this block (in hex)\n" + " \"nTx\" : n, (numeric) The number of transactions in the block.\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" "}\n" @@ -805,17 +824,7 @@ UniValue getblock(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - CBlock block; - if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) - throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); - - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) - // Block not found on disk. This could be because we have the block - // header in our index but don't have the block (for example if a - // non-whitelisted node sends us an unrequested long chain of valid - // blocks, we add the headers to our index, but don't accept the - // block). - throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); + const CBlock block = GetBlockChecked(pblockindex); if (verbosity <= 0) { @@ -848,7 +857,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, ss << hash; ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u); stats.nTransactions++; - for (const auto output : outputs) { + for (const auto& output : outputs) { ss << VARINT(output.first + 1); ss << output.second.out.scriptPubKey; ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); @@ -899,7 +908,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) return true; } -UniValue pruneblockchain(const JSONRPCRequest& request) +static UniValue pruneblockchain(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -940,7 +949,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request) else if (height > chainHeight) throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { - LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks."); + LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } @@ -948,7 +957,7 @@ UniValue pruneblockchain(const JSONRPCRequest& request) return uint64_t(height); } -UniValue gettxoutsetinfo(const JSONRPCRequest& request) +static UniValue gettxoutsetinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -958,9 +967,9 @@ UniValue gettxoutsetinfo(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"height\":n, (numeric) The current block height (index)\n" - " \"bestblock\": \"hex\", (string) the best block hash hex\n" - " \"transactions\": n, (numeric) The number of transactions\n" - " \"txouts\": n, (numeric) The number of output transactions\n" + " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n" + " \"transactions\": n, (numeric) The number of transactions with unspent outputs\n" + " \"txouts\": n, (numeric) The number of unspent transaction outputs\n" " \"bogosize\": n, (numeric) A meaningless metric for UTXO set size\n" " \"hash_serialized_2\": \"hash\", (string) The serialized hash\n" " \"disk_size\": n, (numeric) The estimated size of the chainstate on disk\n" @@ -1003,7 +1012,7 @@ UniValue gettxout(const JSONRPCRequest& request) " Note that an unspent output that is spent in the mempool won't appear.\n" "\nResult:\n" "{\n" - " \"bestblock\" : \"hash\", (string) the block hash\n" + " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" " \"value\" : x.xxx, (numeric) The transaction value in " + CURRENCY_UNIT + "\n" " \"scriptPubKey\" : { (json object)\n" @@ -1069,7 +1078,7 @@ UniValue gettxout(const JSONRPCRequest& request) return ret; } -UniValue verifychain(const JSONRPCRequest& request) +static UniValue verifychain(const JSONRPCRequest& request) { int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); @@ -1159,7 +1168,7 @@ static UniValue BIP9SoftForkDesc(const Consensus::Params& consensusParams, Conse return rv; } -void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) { // Deployments with timeout value of 0 are hidden. // A timeout value of 0 guarantees a softfork will never be activated. @@ -1229,7 +1238,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) obj.pushKV("blocks", (int)chainActive.Height()); obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); obj.pushKV("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex()); - obj.pushKV("difficulty", (double)GetDifficulty()); + obj.pushKV("difficulty", (double)GetDifficulty(chainActive.Tip())); obj.pushKV("mediantime", (int64_t)chainActive.Tip()->GetMedianTimePast()); obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), chainActive.Tip())); obj.pushKV("initialblockdownload", IsInitialBlockDownload()); @@ -1285,7 +1294,7 @@ struct CompareBlocksByHeight } }; -UniValue getchaintips(const JSONRPCRequest& request) +static UniValue getchaintips(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -1402,7 +1411,7 @@ UniValue mempoolInfoToJSON() return ret; } -UniValue getmempoolinfo(const JSONRPCRequest& request) +static UniValue getmempoolinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -1425,7 +1434,7 @@ UniValue getmempoolinfo(const JSONRPCRequest& request) return mempoolInfoToJSON(); } -UniValue preciousblock(const JSONRPCRequest& request) +static UniValue preciousblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1463,7 +1472,7 @@ UniValue preciousblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue invalidateblock(const JSONRPCRequest& request) +static UniValue invalidateblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1502,7 +1511,7 @@ UniValue invalidateblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue reconsiderblock(const JSONRPCRequest& request) +static UniValue reconsiderblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -1540,7 +1549,7 @@ UniValue reconsiderblock(const JSONRPCRequest& request) return NullUniValue; } -UniValue getchaintxstats(const JSONRPCRequest& request) +static UniValue getchaintxstats(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( @@ -1614,7 +1623,285 @@ UniValue getchaintxstats(const JSONRPCRequest& request) return ret; } -UniValue savemempool(const JSONRPCRequest& request) +template<typename T> +static T CalculateTruncatedMedian(std::vector<T>& scores) +{ + size_t size = scores.size(); + if (size == 0) { + return 0; + } + + std::sort(scores.begin(), scores.end()); + if (size % 2 == 0) { + return (scores[size / 2 - 1] + scores[size / 2]) / 2; + } else { + return scores[size / 2]; + } +} + +template<typename T> +static inline bool SetHasKeys(const std::set<T>& set) {return false;} +template<typename T, typename Tk, typename... Args> +static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args&... args) +{ + return (set.count(key) != 0) || SetHasKeys(set, args...); +} + +// outpoint (needed for the utxo index) + nHeight + fCoinBase +static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); + +static UniValue getblockstats(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { + throw std::runtime_error( + "getblockstats hash_or_height ( stats )\n" + "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" + "It won't work for some heights with pruning.\n" + "It won't work without -txindex for utxo_size_inc, *fee or *feerate stats.\n" + "\nArguments:\n" + "1. \"hash_or_height\" (string or numeric, required) The block hash or height of the target block\n" + "2. \"stats\" (array, optional) Values to plot, by default all values (see result below)\n" + " [\n" + " \"height\", (string, optional) Selected statistic\n" + " \"time\", (string, optional) Selected statistic\n" + " ,...\n" + " ]\n" + "\nResult:\n" + "{ (json object)\n" + " \"avgfee\": xxxxx, (numeric) Average fee in the block\n" + " \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n" + " \"avgtxsize\": xxxxx, (numeric) Average transaction size\n" + " \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n" + " \"height\": xxxxx, (numeric) The height of the block\n" + " \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n" + " \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n" + " \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n" + " \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n" + " \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n" + " \"medianfeerate\": xxxxx, (numeric) Truncated median feerate (in satoshis per virtual byte)\n" + " \"mediantime\": xxxxx, (numeric) The block median time past\n" + " \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n" + " \"minfee\": xxxxx, (numeric) Minimum fee in the block\n" + " \"minfeerate\": xxxxx, (numeric) Minimum feerate (in satoshis per virtual byte)\n" + " \"mintxsize\": xxxxx, (numeric) Minimum transaction size\n" + " \"outs\": xxxxx, (numeric) The number of outputs\n" + " \"subsidy\": xxxxx, (numeric) The block subsidy\n" + " \"swtotal_size\": xxxxx, (numeric) Total size of all segwit transactions\n" + " \"swtotal_weight\": xxxxx, (numeric) Total weight of all segwit transactions divided by segwit scale factor (4)\n" + " \"swtxs\": xxxxx, (numeric) The number of segwit transactions\n" + " \"time\": xxxxx, (numeric) The block time\n" + " \"total_out\": xxxxx, (numeric) Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee])\n" + " \"total_size\": xxxxx, (numeric) Total size of all non-coinbase transactions\n" + " \"total_weight\": xxxxx, (numeric) Total weight of all non-coinbase transactions divided by segwit scale factor (4)\n" + " \"totalfee\": xxxxx, (numeric) The fee total\n" + " \"txs\": xxxxx, (numeric) The number of transactions (excluding coinbase)\n" + " \"utxo_increase\": xxxxx, (numeric) The increase/decrease in the number of unspent outputs\n" + " \"utxo_size_inc\": xxxxx, (numeric) The increase/decrease in size for the utxo index (not discounting op_return and similar)\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + ); + } + + LOCK(cs_main); + + CBlockIndex* pindex; + if (request.params[0].isNum()) { + const int height = request.params[0].get_int(); + const int current_tip = chainActive.Height(); + if (height < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); + } + if (height > current_tip) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip)); + } + + pindex = chainActive[height]; + } else { + const std::string strHash = request.params[0].get_str(); + const uint256 hash(uint256S(strHash)); + pindex = LookupBlockIndex(hash); + if (!pindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + if (!chainActive.Contains(pindex)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString())); + } + } + + assert(pindex != nullptr); + + std::set<std::string> stats; + if (!request.params[1].isNull()) { + const UniValue stats_univalue = request.params[1].get_array(); + for (unsigned int i = 0; i < stats_univalue.size(); i++) { + const std::string stat = stats_univalue[i].get_str(); + stats.insert(stat); + } + } + + const CBlock block = GetBlockChecked(pindex); + + const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default) + const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; + const bool do_medianfee = do_all || stats.count("medianfee") != 0; + const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0; + const bool loop_inputs = do_all || do_medianfee || do_medianfeerate || + SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate"); + const bool loop_outputs = do_all || loop_inputs || stats.count("total_out"); + const bool do_calculate_size = do_mediantxsize || + SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size"); + const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "medianfeerate", "minfeerate", "maxfeerate"); + const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight"); + + CAmount maxfee = 0; + CAmount maxfeerate = 0; + CAmount minfee = MAX_MONEY; + CAmount minfeerate = MAX_MONEY; + CAmount total_out = 0; + CAmount totalfee = 0; + int64_t inputs = 0; + int64_t maxtxsize = 0; + int64_t mintxsize = MAX_BLOCK_SERIALIZED_SIZE; + int64_t outputs = 0; + int64_t swtotal_size = 0; + int64_t swtotal_weight = 0; + int64_t swtxs = 0; + int64_t total_size = 0; + int64_t total_weight = 0; + int64_t utxo_size_inc = 0; + std::vector<CAmount> fee_array; + std::vector<CAmount> feerate_array; + std::vector<int64_t> txsize_array; + + for (const auto& tx : block.vtx) { + outputs += tx->vout.size(); + + CAmount tx_total_out = 0; + if (loop_outputs) { + for (const CTxOut& out : tx->vout) { + tx_total_out += out.nValue; + utxo_size_inc += GetSerializeSize(out, SER_NETWORK, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; + } + } + + if (tx->IsCoinBase()) { + continue; + } + + inputs += tx->vin.size(); // Don't count coinbase's fake input + total_out += tx_total_out; // Don't count coinbase reward + + int64_t tx_size = 0; + if (do_calculate_size) { + + tx_size = tx->GetTotalSize(); + if (do_mediantxsize) { + txsize_array.push_back(tx_size); + } + maxtxsize = std::max(maxtxsize, tx_size); + mintxsize = std::min(mintxsize, tx_size); + total_size += tx_size; + } + + int64_t weight = 0; + if (do_calculate_weight) { + weight = GetTransactionWeight(*tx); + total_weight += weight; + } + + if (do_calculate_sw && tx->HasWitness()) { + ++swtxs; + swtotal_size += tx_size; + swtotal_weight += weight; + } + + if (loop_inputs) { + + if (!g_txindex) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires -txindex enabled"); + } + CAmount tx_total_in = 0; + for (const CTxIn& in : tx->vin) { + CTransactionRef tx_in; + uint256 hashBlock; + if (!GetTransaction(in.prevout.hash, tx_in, Params().GetConsensus(), hashBlock, false)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, std::string("Unexpected internal error (tx index seems corrupt)")); + } + + CTxOut prevoutput = tx_in->vout[in.prevout.n]; + + tx_total_in += prevoutput.nValue; + utxo_size_inc -= GetSerializeSize(prevoutput, SER_NETWORK, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; + } + + CAmount txfee = tx_total_in - tx_total_out; + assert(MoneyRange(txfee)); + if (do_medianfee) { + fee_array.push_back(txfee); + } + maxfee = std::max(maxfee, txfee); + minfee = std::min(minfee, txfee); + totalfee += txfee; + + // New feerate uses satoshis per virtual byte instead of per serialized byte + CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0; + if (do_medianfeerate) { + feerate_array.push_back(feerate); + } + maxfeerate = std::max(maxfeerate, feerate); + minfeerate = std::min(minfeerate, feerate); + } + } + + UniValue ret_all(UniValue::VOBJ); + ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0); + ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte + ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); + ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); + ret_all.pushKV("height", (int64_t)pindex->nHeight); + ret_all.pushKV("ins", inputs); + ret_all.pushKV("maxfee", maxfee); + ret_all.pushKV("maxfeerate", maxfeerate); + ret_all.pushKV("maxtxsize", maxtxsize); + ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); + ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array)); + ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); + ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); + ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee); + ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate); + ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize); + ret_all.pushKV("outs", outputs); + ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); + ret_all.pushKV("swtotal_size", swtotal_size); + ret_all.pushKV("swtotal_weight", swtotal_weight); + ret_all.pushKV("swtxs", swtxs); + ret_all.pushKV("time", pindex->GetBlockTime()); + ret_all.pushKV("total_out", total_out); + ret_all.pushKV("total_size", total_size); + ret_all.pushKV("total_weight", total_weight); + ret_all.pushKV("totalfee", totalfee); + ret_all.pushKV("txs", (int64_t)block.vtx.size()); + ret_all.pushKV("utxo_increase", outputs - inputs); + ret_all.pushKV("utxo_size_inc", utxo_size_inc); + + if (do_all) { + return ret_all; + } + + UniValue ret(UniValue::VOBJ); + for (const std::string& stat : stats) { + const UniValue& value = ret_all[stat]; + if (value.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid selected statistic %s", stat)); + } + ret.pushKV(stat, value); + } + return ret; +} + +static UniValue savemempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( @@ -1637,11 +1924,296 @@ UniValue savemempool(const JSONRPCRequest& request) return NullUniValue; } +//! Search for a given set of pubkey scripts +bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) { + scan_progress = 0; + count = 0; + while (cursor->Valid()) { + COutPoint key; + Coin coin; + if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false; + if (++count % 8192 == 0) { + boost::this_thread::interruption_point(); + if (should_abort) { + // allow to abort the scan via the abort reference + return false; + } + } + if (count % 256 == 0) { + // update progress reference every 256 item + uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1); + scan_progress = (int)(high * 100.0 / 65536.0 + 0.5); + } + if (needles.count(coin.out.scriptPubKey)) { + out_results.emplace(key, coin); + } + cursor->Next(); + } + scan_progress = 100; + return true; +} + +/** RAII object to prevent concurrency issue when scanning the txout set */ +static std::mutex g_utxosetscan; +static std::atomic<int> g_scan_progress; +static std::atomic<bool> g_scan_in_progress; +static std::atomic<bool> g_should_abort_scan; +class CoinsViewScanReserver +{ +private: + bool m_could_reserve; +public: + explicit CoinsViewScanReserver() : m_could_reserve(false) {} + + bool reserve() { + assert (!m_could_reserve); + std::lock_guard<std::mutex> lock(g_utxosetscan); + if (g_scan_in_progress) { + return false; + } + g_scan_in_progress = true; + m_could_reserve = true; + return true; + } + + ~CoinsViewScanReserver() { + if (m_could_reserve) { + std::lock_guard<std::mutex> lock(g_utxosetscan); + g_scan_in_progress = false; + } + } +}; + +static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" }; + +enum class OutputScriptType { + UNKNOWN, + P2PK, + P2PKH, + P2SH_P2WPKH, + P2WPKH +}; + +static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype) +{ + if (outputtype == "P2PK") return OutputScriptType::P2PK; + else if (outputtype == "P2PKH") return OutputScriptType::P2PKH; + else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH; + else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH; + else return OutputScriptType::UNKNOWN; +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type) +{ + switch (type) { + case OutputScriptType::P2PKH: return key.GetID(); + case OutputScriptType::P2SH_P2WPKH: + case OutputScriptType::P2WPKH: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + if (type == OutputScriptType::P2SH_P2WPKH) { + CScript witprog = GetScriptForDestination(witdest); + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +UniValue scantxoutset(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "scantxoutset <action> ( <scanobjects> )\n" + "\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n" + "Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n" + "\nArguments:\n" + "1. \"action\" (string, required) The action to execute\n" + " \"start\" for starting a scan\n" + " \"abort\" for aborting the current scan (returns true when abort was successful)\n" + " \"status\" for progress report (in %) of the current scan\n" + "2. \"scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n" + " [\n" + " { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n" + " { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n" + " { \"pubkey\" : (object, optional) Public key\n" + " {\n" + " \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n" + " \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n" + " }\n" + " },\n" + " ]\n" + "\nResult:\n" + "{\n" + " \"unspents\": [\n" + " {\n" + " \"txid\" : \"transactionid\", (string) The transaction id\n" + " \"vout\": n, (numeric) the vout value\n" + " \"scriptPubKey\" : \"script\", (string) the script key\n" + " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" + " \"height\" : n, (numeric) Height of the unspent transaction output\n" + " }\n" + " ,...], \n" + " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" + "]\n" + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); + + UniValue result(UniValue::VOBJ); + if (request.params[0].get_str() == "status") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // no scan in progress + return NullUniValue; + } + result.pushKV("progress", g_scan_progress); + return result; + } else if (request.params[0].get_str() == "abort") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // reserve was possible which means no scan was running + return false; + } + // set the abort flag + g_should_abort_scan = true; + return true; + } else if (request.params[0].get_str() == "start") { + CoinsViewScanReserver reserver; + if (!reserver.reserve()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); + } + std::set<CScript> needles; + CAmount total_in = 0; + + // loop through the scan objects + for (const UniValue& scanobject : request.params[1].get_array().getValues()) { + if (!scanobject.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object"); + } + UniValue address_uni = find_value(scanobject, "address"); + UniValue pubkey_uni = find_value(scanobject, "pubkey"); + UniValue script_uni = find_value(scanobject, "script"); + + // make sure only one object type is present + if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object"); + } else if (!address_uni.isNull() && !address_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value"); + } else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value"); + } else if (!script_uni.isNull() && !script_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value"); + } else if (address_uni.isStr()) { + // type: address + // decode destination and derive the scriptPubKey + // add the script to the scan containers + CTxDestination dest = DecodeDestination(address_uni.get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + CScript script = GetScriptForDestination(dest); + assert(!script.empty()); + needles.insert(script); + } else if (pubkey_uni.isObject()) { + // type: pubkey + // derive script(s) according to the script_type parameter + UniValue script_types_uni = find_value(pubkey_uni, "script_types"); + UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey"); + + // check the script types and use the default if not provided + if (!script_types_uni.isNull() && !script_types_uni.isArray()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array"); + } else if (script_types_uni.isNull()) { + // use the default script types + script_types_uni = UniValue(UniValue::VARR); + for (const char *t : g_default_scantxoutset_script_types) { + script_types_uni.push_back(t); + } + } + + // check the acctual pubkey + if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded"); + } + CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey")); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key"); + } + + // loop through the script types and derive the script + for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) { + OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str()); + if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type"); + CScript script; + if (script_type == OutputScriptType::P2PK) { + // support legacy P2PK scripts + script << ToByteVector(pubkey) << OP_CHECKSIG; + } else { + script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type)); + } + assert(!script.empty()); + needles.insert(script); + } + } else if (script_uni.isStr()) { + // type: script + // check and add the script to the scan containers (needles array) + CScript script(ParseHexV(script_uni, "script")); + // TODO: check script: max length, has OP, is unspenable etc. + needles.insert(script); + } + } + + // Scan the unspent transaction output set for inputs + UniValue unspents(UniValue::VARR); + std::vector<CTxOut> input_txos; + std::map<COutPoint, Coin> coins; + g_should_abort_scan = false; + g_scan_progress = 0; + int64_t count = 0; + std::unique_ptr<CCoinsViewCursor> pcursor; + { + LOCK(cs_main); + FlushStateToDisk(); + pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); + assert(pcursor); + } + bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); + result.pushKV("success", res); + result.pushKV("searched_items", count); + + for (const auto& it : coins) { + const COutPoint& outpoint = it.first; + const Coin& coin = it.second; + const CTxOut& txo = coin.out; + input_txos.push_back(txo); + total_in += txo.nValue; + + UniValue unspent(UniValue::VOBJ); + unspent.pushKV("txid", outpoint.hash.GetHex()); + unspent.pushKV("vout", (int32_t)outpoint.n); + unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end())); + unspent.pushKV("amount", ValueFromAmount(txo.nValue)); + unspent.pushKV("height", (int32_t)coin.nHeight); + + unspents.push_back(unspent); + } + result.pushKV("unspents", unspents); + result.pushKV("total_amount", ValueFromAmount(total_in)); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + } + return result; +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "blockchain", "getblockchaininfo", &getblockchaininfo, {} }, { "blockchain", "getchaintxstats", &getchaintxstats, {"nblocks", "blockhash"} }, + { "blockchain", "getblockstats", &getblockstats, {"hash_or_height", "stats"} }, { "blockchain", "getbestblockhash", &getbestblockhash, {} }, { "blockchain", "getblockcount", &getblockcount, {} }, { "blockchain", "getblock", &getblock, {"blockhash","verbosity|verbose"} }, @@ -1661,6 +2233,7 @@ static const CRPCCommand commands[] = { "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", &preciousblock, {"blockhash"} }, + { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} }, /* Not shown in help */ { "hidden", "invalidateblock", &invalidateblock, {"blockhash"} }, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 960edfd56f..3aa8de2d2b 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -16,7 +16,7 @@ class UniValue; * @return A floating point number that is a multiple of the main net minimum * difficulty (4295032833 hashes). */ -double GetDifficulty(const CBlockIndex* blockindex = nullptr); +double GetDifficulty(const CBlockIndex* blockindex); /** Callback for when block tip changed. */ void RPCNotifyBlockChange(bool ibd, const CBlockIndex *); @@ -34,4 +34,3 @@ UniValue mempoolToJSON(bool fVerbose = false); UniValue blockheaderToJSON(const CBlockIndex* blockindex); #endif - diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 34c41b3b6b..eb6b164075 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -38,6 +38,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendtoaddress", 5 , "replaceable" }, { "sendtoaddress", 6 , "conf_target" }, { "settxfee", 0, "amount" }, + { "sethdseed", 0, "newkeypool" }, { "getreceivedbyaddress", 1, "minconf" }, { "getreceivedbyaccount", 1, "minconf" }, { "getreceivedbylabel", 1, "minconf" }, @@ -51,7 +52,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listreceivedbylabel", 0, "minconf" }, { "listreceivedbylabel", 1, "include_empty" }, { "listreceivedbylabel", 2, "include_watchonly" }, - { "getlabeladdress", 1, "force" }, { "getbalance", 1, "minconf" }, { "getbalance", 2, "include_watchonly" }, { "getblockhash", 0, "height" }, @@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 4, "subtractfeefrom" }, { "sendmany", 5 , "replaceable" }, { "sendmany", 6 , "conf_target" }, + { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, { "createmultisig", 0, "nrequired" }, @@ -109,6 +110,22 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, + { "walletcreatefundedpsbt", 0, "inputs" }, + { "walletcreatefundedpsbt", 1, "outputs" }, + { "walletcreatefundedpsbt", 2, "locktime" }, + { "walletcreatefundedpsbt", 3, "replaceable" }, + { "walletcreatefundedpsbt", 4, "options" }, + { "walletcreatefundedpsbt", 5, "bip32derivs" }, + { "walletprocesspsbt", 1, "sign" }, + { "walletprocesspsbt", 3, "bip32derivs" }, + { "createpsbt", 0, "inputs" }, + { "createpsbt", 1, "outputs" }, + { "createpsbt", 2, "locktime" }, + { "createpsbt", 3, "replaceable" }, + { "combinepsbt", 0, "txs"}, + { "finalizepsbt", 1, "extract"}, + { "converttopsbt", 1, "permitsigdata"}, + { "converttopsbt", 2, "iswitness"}, { "gettxout", 1, "n" }, { "gettxout", 2, "include_mempool" }, { "gettxoutproof", 0, "txids" }, @@ -122,6 +139,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "importmulti", 1, "options" }, { "verifychain", 0, "checklevel" }, { "verifychain", 1, "nblocks" }, + { "getblockstats", 0, "hash_or_height" }, + { "getblockstats", 1, "stats" }, { "pruneblockchain", 0, "height" }, { "keypoolrefill", 0, "newsize" }, { "getrawmempool", 0, "verbose" }, @@ -153,6 +172,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "echojson", 9, "arg9" }, { "rescanblockchain", 0, "start_height"}, { "rescanblockchain", 1, "stop_height"}, + { "createwallet", 1, "disable_private_keys"}, }; class CRPCConvertTable diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index b4bb78e689..e751587dc7 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -10,7 +10,6 @@ #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> -#include <init.h> #include <validation.h> #include <key_io.h> #include <miner.h> @@ -20,6 +19,7 @@ #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> +#include <shutdown.h> #include <txmempool.h> #include <util.h> #include <utilstrencodings.h> @@ -44,7 +44,7 @@ unsigned int ParseConfirmTarget(const UniValue& value) * or from the last difficulty change if 'lookup' is nonpositive. * If 'height' is nonnegative, compute the estimate at the time when a given block was found. */ -UniValue GetNetworkHashPS(int lookup, int height) { +static UniValue GetNetworkHashPS(int lookup, int height) { CBlockIndex *pb = chainActive.Tip(); if (height >= 0 && height < chainActive.Height()) @@ -81,7 +81,7 @@ UniValue GetNetworkHashPS(int lookup, int height) { return workDiff.getdouble() / timeDiff; } -UniValue getnetworkhashps(const JSONRPCRequest& request) +static UniValue getnetworkhashps(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( @@ -151,7 +151,7 @@ UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGen return blockHashes; } -UniValue generatetoaddress(const JSONRPCRequest& request) +static UniValue generatetoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( @@ -185,7 +185,7 @@ UniValue generatetoaddress(const JSONRPCRequest& request) return generateBlocks(coinbaseScript, nGenerate, nMaxTries, false); } -UniValue getmininginfo(const JSONRPCRequest& request) +static UniValue getmininginfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -214,7 +214,7 @@ UniValue getmininginfo(const JSONRPCRequest& request) obj.pushKV("blocks", (int)chainActive.Height()); obj.pushKV("currentblockweight", (uint64_t)nLastBlockWeight); obj.pushKV("currentblocktx", (uint64_t)nLastBlockTx); - obj.pushKV("difficulty", (double)GetDifficulty()); + obj.pushKV("difficulty", (double)GetDifficulty(chainActive.Tip())); obj.pushKV("networkhashps", getnetworkhashps(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); obj.pushKV("chain", Params().NetworkIDString()); @@ -224,7 +224,7 @@ UniValue getmininginfo(const JSONRPCRequest& request) // NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts -UniValue prioritisetransaction(const JSONRPCRequest& request) +static UniValue prioritisetransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( @@ -278,7 +278,7 @@ static UniValue BIP22ValidationResult(const CValidationState& state) return "valid?"; } -std::string gbt_vb_name(const Consensus::DeploymentPos pos) { +static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { const struct VBDeploymentInfo& vbinfo = VersionBitsDeploymentInfo[pos]; std::string s = vbinfo.name; if (!vbinfo.gbt_force) { @@ -287,7 +287,7 @@ std::string gbt_vb_name(const Consensus::DeploymentPos pos) { return s; } -UniValue getblocktemplate(const JSONRPCRequest& request) +static UniValue getblocktemplate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -337,7 +337,6 @@ UniValue getblocktemplate(const JSONRPCRequest& request) " \"fee\": n, (numeric) difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one\n" " \"sigops\" : n, (numeric) total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero\n" " \"weight\" : n, (numeric) total transaction weight, as counted for purposes of block limits\n" - " \"required\" : true|false (boolean) if provided and true, this transaction must be in the final block\n" " }\n" " ,...\n" " ],\n" @@ -525,6 +524,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request) // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } + assert(pindexPrev); CBlock* pblock = &pblocktemplate->block; // pointer for convenience const Consensus::Params& consensusParams = Params().GetConsensus(); @@ -694,7 +694,7 @@ protected: } }; -UniValue submitblock(const JSONRPCRequest& request) +static UniValue submitblock(const JSONRPCRequest& request) { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { @@ -724,7 +724,6 @@ UniValue submitblock(const JSONRPCRequest& request) } uint256 hash = block.GetHash(); - bool fBlockPresent = false; { LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(hash); @@ -735,8 +734,6 @@ UniValue submitblock(const JSONRPCRequest& request) if (pindex->nStatus & BLOCK_FAILED_MASK) { return "duplicate-invalid"; } - // Otherwise, we might only have the header - process the block before returning - fBlockPresent = true; } } @@ -748,13 +745,15 @@ UniValue submitblock(const JSONRPCRequest& request) } } + bool new_block; submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); - bool fAccepted = ProcessNewBlock(Params(), blockptr, true, nullptr); + bool accepted = ProcessNewBlock(Params(), blockptr, /* fForceProcessing */ true, /* fNewBlock */ &new_block); UnregisterValidationInterface(&sc); - if (fBlockPresent) { - if (fAccepted && !sc.found) { - return "duplicate-inconclusive"; + if (!new_block) { + if (!accepted) { + // TODO Maybe pass down fNewBlock to AcceptBlockHeader, so it is properly set to true in this case? + return "invalid"; } return "duplicate"; } @@ -764,13 +763,13 @@ UniValue submitblock(const JSONRPCRequest& request) return BIP22ValidationResult(sc.state); } -UniValue estimatefee(const JSONRPCRequest& request) +static UniValue estimatefee(const JSONRPCRequest& request) { throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee was removed in v0.17.\n" "Clients should use estimatesmartfee."); } -UniValue estimatesmartfee(const JSONRPCRequest& request) +static UniValue estimatesmartfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -831,7 +830,7 @@ UniValue estimatesmartfee(const JSONRPCRequest& request) return result; } -UniValue estimaterawfee(const JSONRPCRequest& request) +static UniValue estimaterawfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6754407dbd..09812bb980 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -7,12 +7,12 @@ #include <clientversion.h> #include <core_io.h> #include <crypto/ripemd160.h> -#include <init.h> #include <key_io.h> #include <validation.h> #include <httpserver.h> #include <net.h> #include <netbase.h> +#include <outputtype.h> #include <rpc/blockchain.h> #include <rpc/server.h> #include <rpc/util.h> @@ -33,7 +33,7 @@ #include <univalue.h> -UniValue validateaddress(const JSONRPCRequest& request) +static UniValue validateaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -78,7 +78,7 @@ UniValue validateaddress(const JSONRPCRequest& request) ret.pushKV("address", currentAddress); CScript scriptPubKey = GetScriptForDestination(dest); - ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));; + ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); @@ -90,11 +90,11 @@ UniValue validateaddress(const JSONRPCRequest& request) // Needed even with !ENABLE_WALLET, to pass (ignored) pointers around class CWallet; -UniValue createmultisig(const JSONRPCRequest& request) +static UniValue createmultisig(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) + if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { - std::string msg = "createmultisig nrequired [\"key\",...]\n" + std::string msg = "createmultisig nrequired [\"key\",...] ( \"address_type\" )\n" "\nCreates a multi-signature address with n signature of m keys required.\n" "It returns a json object with the address and redeemScript.\n" "\nArguments:\n" @@ -104,6 +104,7 @@ UniValue createmultisig(const JSONRPCRequest& request) " \"key\" (string) The hex-encoded public key\n" " ,...\n" " ]\n" + "3. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is legacy.\n" "\nResult:\n" "{\n" @@ -134,18 +135,27 @@ UniValue createmultisig(const JSONRPCRequest& request) } } + // Get the output type + OutputType output_type = OutputType::LEGACY; + if (!request.params[2].isNull()) { + if (!ParseOutputType(request.params[2].get_str(), output_type)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); + } + } + // Construct using pay-to-script-hash: - CScript inner = CreateMultisigRedeemscript(required, pubkeys); - CScriptID innerID(inner); + const CScript inner = CreateMultisigRedeemscript(required, pubkeys); + CBasicKeyStore keystore; + const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type); UniValue result(UniValue::VOBJ); - result.pushKV("address", EncodeDestination(innerID)); + result.pushKV("address", EncodeDestination(dest)); result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); return result; } -UniValue verifymessage(const JSONRPCRequest& request) +static UniValue verifymessage(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( @@ -201,7 +211,7 @@ UniValue verifymessage(const JSONRPCRequest& request) return (pubkey.GetID() == *keyID); } -UniValue signmessagewithprivkey(const JSONRPCRequest& request) +static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 2) throw std::runtime_error( @@ -240,7 +250,7 @@ UniValue signmessagewithprivkey(const JSONRPCRequest& request) return EncodeBase64(vchSig.data(), vchSig.size()); } -UniValue setmocktime(const JSONRPCRequest& request) +static UniValue setmocktime(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -299,7 +309,7 @@ static std::string RPCMallocInfo() } #endif -UniValue getmemoryinfo(const JSONRPCRequest& request) +static UniValue getmemoryinfo(const JSONRPCRequest& request) { /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" @@ -346,21 +356,22 @@ UniValue getmemoryinfo(const JSONRPCRequest& request) } } -uint32_t getCategoryMask(UniValue cats) { +static void EnableOrDisableLogCategories(UniValue cats, bool enable) { cats = cats.get_array(); - uint32_t mask = 0; for (unsigned int i = 0; i < cats.size(); ++i) { - uint32_t flag = 0; std::string cat = cats[i].get_str(); - if (!GetLogCategory(&flag, &cat)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); + + bool success; + if (enable) { + success = g_logger->EnableCategory(cat); + } else { + success = g_logger->DisableCategory(cat); } - if (flag == BCLog::NONE) { - return 0; + + if (!success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); } - mask |= flag; } - return mask; } UniValue logging(const JSONRPCRequest& request) @@ -399,25 +410,25 @@ UniValue logging(const JSONRPCRequest& request) ); } - uint32_t originalLogCategories = logCategories; + uint32_t original_log_categories = g_logger->GetCategoryMask(); if (request.params[0].isArray()) { - logCategories |= getCategoryMask(request.params[0]); + EnableOrDisableLogCategories(request.params[0], true); } - if (request.params[1].isArray()) { - logCategories &= ~getCategoryMask(request.params[1]); + EnableOrDisableLogCategories(request.params[1], false); } + uint32_t updated_log_categories = g_logger->GetCategoryMask(); + uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; // Update libevent logging if BCLog::LIBEVENT has changed. // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false, // in which case we should clear the BCLog::LIBEVENT flag. // Throw an error if the user has explicitly asked to change only the libevent // flag and it failed. - uint32_t changedLogCategories = originalLogCategories ^ logCategories; - if (changedLogCategories & BCLog::LIBEVENT) { - if (!UpdateHTTPServerLogging(logCategories & BCLog::LIBEVENT)) { - logCategories &= ~BCLog::LIBEVENT; - if (changedLogCategories == BCLog::LIBEVENT) { + if (changed_log_categories & BCLog::LIBEVENT) { + if (!UpdateHTTPServerLogging(g_logger->WillLogCategory(BCLog::LIBEVENT))) { + g_logger->DisableCategory(BCLog::LIBEVENT); + if (changed_log_categories == BCLog::LIBEVENT) { throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1."); } } @@ -432,7 +443,7 @@ UniValue logging(const JSONRPCRequest& request) return result; } -UniValue echo(const JSONRPCRequest& request) +static UniValue echo(const JSONRPCRequest& request) { if (request.fHelp) throw std::runtime_error( diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index fee2b765ba..8fa56e9335 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -23,7 +23,7 @@ #include <univalue.h> -UniValue getconnectioncount(const JSONRPCRequest& request) +static UniValue getconnectioncount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -42,7 +42,7 @@ UniValue getconnectioncount(const JSONRPCRequest& request) return (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL); } -UniValue ping(const JSONRPCRequest& request) +static UniValue ping(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -65,7 +65,7 @@ UniValue ping(const JSONRPCRequest& request) return NullUniValue; } -UniValue getpeerinfo(const JSONRPCRequest& request) +static UniValue getpeerinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -190,7 +190,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request) return ret; } -UniValue addnode(const JSONRPCRequest& request) +static UniValue addnode(const JSONRPCRequest& request) { std::string strCommand; if (!request.params[1].isNull()) @@ -237,7 +237,7 @@ UniValue addnode(const JSONRPCRequest& request) return NullUniValue; } -UniValue disconnectnode(const JSONRPCRequest& request) +static UniValue disconnectnode(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) throw std::runtime_error( @@ -280,7 +280,7 @@ UniValue disconnectnode(const JSONRPCRequest& request) return NullUniValue; } -UniValue getaddednodeinfo(const JSONRPCRequest& request) +static UniValue getaddednodeinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( @@ -347,7 +347,7 @@ UniValue getaddednodeinfo(const JSONRPCRequest& request) return ret; } -UniValue getnettotals(const JSONRPCRequest& request) +static UniValue getnettotals(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) throw std::runtime_error( @@ -413,7 +413,7 @@ static UniValue GetNetworksInfo() return networks; } -UniValue getnetworkinfo(const JSONRPCRequest& request) +static UniValue getnetworkinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -475,7 +475,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) UniValue localAddresses(UniValue::VARR); { LOCK(cs_mapLocalHost); - for (const std::pair<CNetAddr, LocalServiceInfo> &item : mapLocalHost) + for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost) { UniValue rec(UniValue::VOBJ); rec.pushKV("address", item.first.ToString()); @@ -489,7 +489,7 @@ UniValue getnetworkinfo(const JSONRPCRequest& request) return obj; } -UniValue setban(const JSONRPCRequest& request) +static UniValue setban(const JSONRPCRequest& request) { std::string strCommand; if (!request.params[1].isNull()) @@ -553,7 +553,7 @@ UniValue setban(const JSONRPCRequest& request) return NullUniValue; } -UniValue listbanned(const JSONRPCRequest& request) +static UniValue listbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -586,7 +586,7 @@ UniValue listbanned(const JSONRPCRequest& request) return bannedAddresses; } -UniValue clearbanned(const JSONRPCRequest& request) +static UniValue clearbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( @@ -604,7 +604,7 @@ UniValue clearbanned(const JSONRPCRequest& request) return NullUniValue; } -UniValue setnetworkactive(const JSONRPCRequest& request) +static UniValue setnetworkactive(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 4a265735d2..6954aed252 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -46,7 +46,6 @@ enum RPCErrorCode //! General application defined errors RPC_MISC_ERROR = -1, //!< std::exception thrown in command handling - RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode RPC_TYPE_ERROR = -3, //!< Unexpected type was passed as parameter RPC_INVALID_ADDRESS_OR_KEY = -5, //!< Invalid address or key RPC_OUT_OF_MEMORY = -7, //!< Ran out of memory during operation @@ -88,6 +87,9 @@ enum RPCErrorCode //! Backwards compatible aliases RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME, + + //! Unused reserved codes, kept around for backwards compatibility. Do not reuse. + RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode }; UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 0cb34a0cca..bb94e11fea 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -5,10 +5,10 @@ #include <chain.h> #include <coins.h> +#include <compat/byteswap.h> #include <consensus/validation.h> #include <core_io.h> #include <index/txindex.h> -#include <init.h> #include <keystore.h> #include <validation.h> #include <validationinterface.h> @@ -19,7 +19,6 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/rawtransaction.h> -#include <rpc/safemode.h> #include <rpc/server.h> #include <script/script.h> #include <script/script_error.h> @@ -38,7 +37,7 @@ #include <univalue.h> -void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // @@ -64,7 +63,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) } } -UniValue getrawtransaction(const JSONRPCRequest& request) +static UniValue getrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( @@ -204,7 +203,7 @@ UniValue getrawtransaction(const JSONRPCRequest& request) return result; } -UniValue gettxoutproof(const JSONRPCRequest& request) +static UniValue gettxoutproof(const JSONRPCRequest& request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) throw std::runtime_error( @@ -298,7 +297,7 @@ UniValue gettxoutproof(const JSONRPCRequest& request) return strHex; } -UniValue verifytxoutproof(const JSONRPCRequest& request) +static UniValue verifytxoutproof(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -308,7 +307,7 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) "\nArguments:\n" "1. \"proof\" (string, required) The hex-encoded proof generated by gettxoutproof\n" "\nResult:\n" - "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof is invalid\n" + "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" ); CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); @@ -325,89 +324,39 @@ UniValue verifytxoutproof(const JSONRPCRequest& request) LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !chainActive.Contains(pindex)) { + if (!pindex || !chainActive.Contains(pindex) || pindex->nTx == 0) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } - for (const uint256& hash : vMatch) - res.push_back(hash.GetHex()); + // Check if proof is valid, only add results if so + if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { + for (const uint256& hash : vMatch) { + res.push_back(hash.GetHex()); + } + } + return res; } -UniValue createrawtransaction(const JSONRPCRequest& request) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - throw std::runtime_error( - // clang-format off - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" - "\nCreate a transaction spending the given inputs and creating new outputs.\n" - "Outputs can be addresses or data.\n" - "Returns hex-encoded raw transaction.\n" - "Note that the transaction's inputs are not signed, and\n" - "it is not stored in the wallet or transmitted to the network.\n" - - "\nArguments:\n" - "1. \"inputs\" (array, required) A json array of json objects\n" - " [\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"sequence\":n (numeric, optional) The sequence number\n" - " } \n" - " ,...\n" - " ]\n" - "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" - " [\n" - " {\n" - " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" - " },\n" - " {\n" - " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" - " }\n" - " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.\n" - " ]\n" - "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" - "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" - "\nResult:\n" - "\"transaction\" (string) hex string of the transaction\n" - - "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - // clang-format on - ); - } - - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL - }, true - ); - if (request.params[0].isNull() || request.params[1].isNull()) + if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); - UniValue inputs = request.params[0].get_array(); - const bool outputs_is_obj = request.params[1].isObject(); - UniValue outputs = outputs_is_obj ? - request.params[1].get_obj() : - request.params[1].get_array(); + UniValue inputs = inputs_in.get_array(); + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); CMutableTransaction rawTx; - if (!request.params[2].isNull()) { - int64_t nLockTime = request.params[2].get_int64(); + if (!locktime.isNull()) { + int64_t nLockTime = locktime.get_int64(); if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } - bool rbfOptIn = request.params[3].isTrue(); + bool rbfOptIn = rbf.isTrue(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; @@ -487,14 +436,75 @@ UniValue createrawtransaction(const JSONRPCRequest& request) } } - if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { + if (!rbf.isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } + return rawTx; +} + +static UniValue createrawtransaction(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { + throw std::runtime_error( + // clang-format off + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + "\nCreate a transaction spending the given inputs and creating new outputs.\n" + "Outputs can be addresses or data.\n" + "Returns hex-encoded raw transaction.\n" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network.\n" + + "\nArguments:\n" + "1. \"inputs\" (array, required) A json array of json objects\n" + " [\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"sequence\":n (numeric, optional) The sequence number\n" + " } \n" + " ,...\n" + " ]\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" + " {\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" + " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" + "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" + "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "\nResult:\n" + "\"transaction\" (string) hex string of the transaction\n" + + "\nExamples:\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + // clang-format on + ); + } + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + return EncodeHexTx(rawTx); } -UniValue decoderawtransaction(const JSONRPCRequest& request) +static UniValue decoderawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -570,7 +580,7 @@ UniValue decoderawtransaction(const JSONRPCRequest& request) return result; } -UniValue decodescript(const JSONRPCRequest& request) +static UniValue decodescript(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( @@ -638,9 +648,7 @@ UniValue decodescript(const JSONRPCRequest& request) } else { // Scripts that are not fit for P2WPKH are encoded as P2WSH. // Newer segwit program versions should be considered when then become available. - uint256 scriptHash; - CSHA256().Write(script.data(), script.size()).Finalize(scriptHash.begin()); - segwitScr = GetScriptForDestination(WitnessV0ScriptHash(scriptHash)); + segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); } ScriptPubKeyToUniv(segwitScr, sr, true); sr.pushKV("p2sh-segwit", EncodeDestination(CScriptID(segwitScr))); @@ -668,7 +676,7 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -UniValue combinerawtransaction(const JSONRPCRequest& request) +static UniValue combinerawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -737,19 +745,17 @@ UniValue combinerawtransaction(const JSONRPCRequest& request) if (coin.IsSpent()) { throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent"); } - const CScript& prevPubKey = coin.out.scriptPubKey; - const CAmount& amount = coin.out.nValue; - SignatureData sigdata; // ... and merge in other signatures: for (const CMutableTransaction& txv : txVariants) { if (txv.vin.size() > i) { - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(txv, i)); + sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out)); } } + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); - UpdateTransaction(mergedTx, i, sigdata); + UpdateInput(txin, sigdata); } return EncodeHexTx(mergedTx); @@ -812,7 +818,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } Coin newcoin; newcoin.out.scriptPubKey = scriptPubKey; - newcoin.out.nValue = 0; + newcoin.out.nValue = MAX_MONEY; if (prevOut.exists("amount")) { newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount")); } @@ -839,23 +845,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } } - int nHashType = SIGHASH_ALL; - if (!hashType.isNull()) { - static std::map<std::string, int> mapSigHashValues = { - {std::string("ALL"), int(SIGHASH_ALL)}, - {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, - {std::string("NONE"), int(SIGHASH_NONE)}, - {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, - {std::string("SINGLE"), int(SIGHASH_SINGLE)}, - {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, - }; - std::string strHashType = hashType.get_str(); - if (mapSigHashValues.count(strHashType)) { - nHashType = mapSigHashValues[strHashType]; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); - } - } + int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); @@ -876,14 +866,18 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival const CScript& prevPubKey = coin.out.scriptPubKey; const CAmount& amount = coin.out.nValue; - SignatureData sigdata; + SignatureData sigdata = DataFromTransaction(mtx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); } - sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i)); - UpdateTransaction(mtx, i, sigdata); + UpdateInput(txin, sigdata); + + // amount must be specified for valid segwit signature + if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin.out.ToString())); + } ScriptError serror = SCRIPT_ERR_OK; if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { @@ -907,7 +901,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival return result; } -UniValue signrawtransactionwithkey(const JSONRPCRequest& request) +static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( @@ -989,7 +983,8 @@ UniValue signrawtransactionwithkey(const JSONRPCRequest& request) UniValue signrawtransaction(const JSONRPCRequest& request) { #ifdef ENABLE_WALLET - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); #endif if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) @@ -1085,7 +1080,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) } } -UniValue sendrawtransaction(const JSONRPCRequest& request) +static UniValue sendrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( @@ -1108,8 +1103,6 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") ); - ObserveSafeMode(); - std::promise<void> promise; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -1182,7 +1175,7 @@ UniValue sendrawtransaction(const JSONRPCRequest& request) return hashTx.GetHex(); } -UniValue testmempoolaccept(const JSONRPCRequest& request) +static UniValue testmempoolaccept(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( @@ -1217,8 +1210,6 @@ UniValue testmempoolaccept(const JSONRPCRequest& request) ); } - ObserveSafeMode(); - RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VBOOL}); if (request.params[0].get_array().size() != 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Array must contain exactly one raw transaction for now"); @@ -1263,6 +1254,553 @@ UniValue testmempoolaccept(const JSONRPCRequest& request) return result; } +static std::string WriteHDKeypath(std::vector<uint32_t>& keypath) +{ + std::string keypath_str = "m"; + for (uint32_t num : keypath) { + keypath_str += "/"; + bool hardened = false; + if (num & 0x80000000) { + hardened = true; + num &= ~0x80000000; + } + + keypath_str += std::to_string(num); + if (hardened) { + keypath_str += "'"; + } + } + return keypath_str; +} + +UniValue decodepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "decodepsbt \"psbt\"\n" + "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n" + + "\nArguments:\n" + "1. \"psbt\" (string, required) The PSBT base64 string\n" + + "\nResult:\n" + "{\n" + " \"tx\" : { (json object) The decoded network-serialized unsigned transaction.\n" + " ... The layout is the same as the output of decoderawtransaction.\n" + " },\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " \"inputs\" : [ (array of json objects)\n" + " {\n" + " \"non_witness_utxo\" : { (json object, optional) Decoded network transaction for non-witness UTXOs\n" + " ...\n" + " },\n" + " \"witness_utxo\" : { (json object, optional) Transaction output for witness UTXOs\n" + " \"amount\" : x.xxx, (numeric) The value in " + CURRENCY_UNIT + "\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"address\" : \"address\" (string) Bitcoin address if there is one\n" + " }\n" + " },\n" + " \"partial_signatures\" : { (json object, optional)\n" + " \"pubkey\" : \"signature\", (string) The public key and signature that corresponds to it.\n" + " ,...\n" + " }\n" + " \"sighash\" : \"type\", (string, optional) The sighash type to be used\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : { (json object, optional)\n" + " \"pubkey\" : { (json object, optional) The public key with the derivation path as the value.\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " ,...\n" + " }\n" + " \"final_scriptsig\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " }\n" + " \"final_scriptwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"outputs\" : [ (array of json objects)\n" + " {\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : [ (array of json objects, optional)\n" + " {\n" + " \"pubkey\" : \"pubkey\", (string) The public key this path corresponds to\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"fee\" : fee (numeric, optional) The transaction fee paid if all UTXOs slots in the PSBT have been filled.\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("decodepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR}); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + UniValue result(UniValue::VOBJ); + + // Add the decoded tx + UniValue tx_univ(UniValue::VOBJ); + TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); + result.pushKV("tx", tx_univ); + + // Unknown data + UniValue unknowns(UniValue::VOBJ); + for (auto entry : psbtx.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + result.pushKV("unknown", unknowns); + + // inputs + CAmount total_in = 0; + bool have_all_utxos = true; + UniValue inputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const PSBTInput& input = psbtx.inputs[i]; + UniValue in(UniValue::VOBJ); + // UTXOs + if (!input.witness_utxo.IsNull()) { + const CTxOut& txout = input.witness_utxo; + + UniValue out(UniValue::VOBJ); + + out.pushKV("amount", ValueFromAmount(txout.nValue)); + total_in += txout.nValue; + + UniValue o(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, o, true); + out.pushKV("scriptPubKey", o); + in.pushKV("witness_utxo", out); + } else if (input.non_witness_utxo) { + UniValue non_wit(UniValue::VOBJ); + TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false); + in.pushKV("non_witness_utxo", non_wit); + total_in += input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue; + } else { + have_all_utxos = false; + } + + // Partial sigs + if (!input.partial_sigs.empty()) { + UniValue partial_sigs(UniValue::VOBJ); + for (const auto& sig : input.partial_sigs) { + partial_sigs.pushKV(HexStr(sig.second.first), HexStr(sig.second.second)); + } + in.pushKV("partial_signatures", partial_sigs); + } + + // Sighash + if (input.sighash_type > 0) { + in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type)); + } + + // Redeem script and witness script + if (!input.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.redeem_script, r, false); + in.pushKV("redeem_script", r); + } + if (!input.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.witness_script, r, false); + in.pushKV("witness_script", r); + } + + // keypaths + if (!input.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : input.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + in.pushKV("bip32_derivs", keypaths); + } + + // Final scriptSig and scriptwitness + if (!input.final_script_sig.empty()) { + UniValue scriptsig(UniValue::VOBJ); + scriptsig.pushKV("asm", ScriptToAsmStr(input.final_script_sig, true)); + scriptsig.pushKV("hex", HexStr(input.final_script_sig)); + in.pushKV("final_scriptSig", scriptsig); + } + if (!input.final_script_witness.IsNull()) { + UniValue txinwitness(UniValue::VARR); + for (const auto& item : input.final_script_witness.stack) { + txinwitness.push_back(HexStr(item.begin(), item.end())); + } + in.pushKV("final_scriptwitness", txinwitness); + } + + // Unknown data + if (input.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : input.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + in.pushKV("unknown", unknowns); + } + + inputs.push_back(in); + } + result.pushKV("inputs", inputs); + + // outputs + CAmount output_value = 0; + UniValue outputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { + const PSBTOutput& output = psbtx.outputs[i]; + UniValue out(UniValue::VOBJ); + // Redeem script and witness script + if (!output.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.redeem_script, r, false); + out.pushKV("redeem_script", r); + } + if (!output.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.witness_script, r, false); + out.pushKV("witness_script", r); + } + + // keypaths + if (!output.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : output.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + out.pushKV("bip32_derivs", keypaths); + } + + // Unknown data + if (output.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : output.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + out.pushKV("unknown", unknowns); + } + + outputs.push_back(out); + + // Fee calculation + output_value += psbtx.tx->vout[i].nValue; + } + result.pushKV("outputs", outputs); + if (have_all_utxos) { + result.pushKV("fee", ValueFromAmount(total_in - output_value)); + } + + return result; +} + +UniValue combinepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "combinepsbt [\"psbt\",...]\n" + "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" + "Implements the Combiner role.\n" + "\nArguments:\n" + "1. \"txs\" (string) A json array of base64 strings of partially signed transactions\n" + " [\n" + " \"psbt\" (string) A base64 string of a PSBT\n" + " ,...\n" + " ]\n" + + "\nResult:\n" + " \"psbt\" (string) The base64-encoded partially signed transaction\n" + "\nExamples:\n" + + HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") + ); + + RPCTypeCheck(request.params, {UniValue::VARR}, true); + + // Unserialize the transactions + std::vector<PartiallySignedTransaction> psbtxs; + UniValue txs = request.params[0].get_array(); + for (unsigned int i = 0; i < txs.size(); ++i) { + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, txs[i].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + psbtxs.push_back(psbtx); + } + + PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one + + // Merge + for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) { + if (*it != merged_psbt) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs do not refer to the same transactions."); + } + merged_psbt.Merge(*it); + } + if (!merged_psbt.IsSane()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Merged PSBT is inconsistent"); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << merged_psbt; + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +UniValue finalizepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "finalizepsbt \"psbt\" ( extract )\n" + "Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n" + "network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n" + "created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n" + "Implements the Finalizer and Extractor roles.\n" + "\nArguments:\n" + "1. \"psbt\" (string) A base64 string of a PSBT\n" + "2. \"extract\" (boolean, optional, default=true) If true and the transaction is complete, \n" + " extract and return the complete transaction in normal network serialization instead of the PSBT.\n" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction if not extracted\n" + " \"hex\" : \"value\", (string) The hex-encoded network transaction if extracted\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("finalizepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); + + // Unserialize the transactions + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + // Get all of the previous transactions + bool complete = true; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs.at(i); + + SignatureData sigdata; + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, sigdata, i, 1); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()); + if (complete && extract) { + CMutableTransaction mtx(*psbtx.tx); + for (unsigned int i = 0; i < mtx.vin.size(); ++i) { + mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig; + mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; + } + ssTx << mtx; + result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + } else { + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + } + result.push_back(Pair("complete", complete)); + + return result; +} + +UniValue createpsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) + throw std::runtime_error( + "createpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + "\nCreates a transaction in the Partially Signed Transaction format.\n" + "Implements the Creator role.\n" + "\nArguments:\n" + "1. \"inputs\" (array, required) A json array of json objects\n" + " [\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"sequence\":n (numeric, optional) The sequence number\n" + " } \n" + " ,...\n" + " ]\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" + " {\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" + " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" + "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" + "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + + HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = rawTx; + for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +UniValue converttopsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "converttopsbt \"hexstring\" ( permitsigdata iswitness )\n" + "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" + "createpsbt and walletcreatefundedpsbt should be used for new applications.\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of a raw transaction\n" + "2. permitsigdata (boolean, optional, default=false) If true, any signatures in the input will be discarded and conversion.\n" + " will continue. If false, RPC will fail if any signatures are present.\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction.\n" + " If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" + " will be tried. If false, only non-witness deserialization wil be tried. Only has an effect if\n" + " permitsigdata is true.\n" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + "\nConvert the transaction to a PSBT\n" + + HelpExampleCli("converttopsbt", "\"rawtransaction\"") + ); + + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); + + // parse hex string from parameter + CMutableTransaction tx; + bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); + bool witness_specified = !request.params[2].isNull(); + bool iswitness = witness_specified ? request.params[2].get_bool() : false; + bool try_witness = permitsigdata ? (witness_specified ? iswitness : true) : false; + bool try_no_witness = permitsigdata ? (witness_specified ? !iswitness : true) : true; + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + // Remove all scriptSigs and scriptWitnesses from inputs + for (CTxIn& input : tx.vin) { + if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && (request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()))) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); + } + input.scriptSig.clear(); + input.scriptWitness.SetNull(); + } + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = tx; + for (unsigned int i = 0; i < tx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < tx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -1275,6 +1813,11 @@ static const CRPCCommand commands[] = { "rawtransactions", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ { "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} }, + { "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} }, + { "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} }, + { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, + { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} }, + { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} }, { "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} }, diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index ec9d1f2cf0..52dccc90e8 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -12,4 +12,7 @@ class UniValue; /** Sign a transaction with the given keystore and previous transactions */ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); +/** Create a transaction from univalue parameters */ +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); + #endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/rpc/safemode.cpp b/src/rpc/safemode.cpp deleted file mode 100644 index 9f3a9d30b8..0000000000 --- a/src/rpc/safemode.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include <rpc/safemode.h> - -#include <rpc/protocol.h> -#include <util.h> -#include <warnings.h> - -void ObserveSafeMode() -{ - std::string warning = GetWarnings("rpc"); - if (warning != "" && !gArgs.GetBoolArg("-disablesafemode", DEFAULT_DISABLE_SAFEMODE)) { - throw JSONRPCError(RPC_FORBIDDEN_BY_SAFE_MODE, std::string("Safe mode: ") + warning); - } -} - diff --git a/src/rpc/safemode.h b/src/rpc/safemode.h deleted file mode 100644 index 8466d6b2f9..0000000000 --- a/src/rpc/safemode.h +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2017 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_RPC_SAFEMODE_H -#define BITCOIN_RPC_SAFEMODE_H - -static const bool DEFAULT_DISABLE_SAFEMODE = true; - -void ObserveSafeMode(); - -#endif // BITCOIN_RPC_SAFEMODE_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index c7c3b1f0d3..b420e9d8b3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -1,14 +1,14 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <rpc/server.h> #include <fs.h> -#include <init.h> #include <key_io.h> #include <random.h> +#include <shutdown.h> #include <sync.h> #include <ui_interface.h> #include <util.h> @@ -23,10 +23,10 @@ #include <memory> // for unique_ptr #include <unordered_map> -static bool fRPCRunning = false; -static bool fRPCInWarmup = true; -static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; +static bool fRPCRunning = false; +static bool fRPCInWarmup GUARDED_BY(cs_rpcWarmup) = true; +static std::string rpcWarmupStatus GUARDED_BY(cs_rpcWarmup) = "RPC server started"; /* Timer-creating functions */ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ @@ -239,7 +239,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest) return "Bitcoin server stopping"; } -UniValue uptime(const JSONRPCRequest& jsonRequest) +static UniValue uptime(const JSONRPCRequest& jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( |