diff options
Diffstat (limited to 'src/rpc')
-rw-r--r-- | src/rpc/blockchain.cpp | 419 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 6 | ||||
-rw-r--r-- | src/rpc/client.cpp | 4 | ||||
-rw-r--r-- | src/rpc/external_signer.cpp | 2 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 102 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 149 | ||||
-rw-r--r-- | src/rpc/net.cpp | 20 | ||||
-rw-r--r-- | src/rpc/node.cpp | 8 | ||||
-rw-r--r-- | src/rpc/output_script.cpp | 122 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 19 | ||||
-rw-r--r-- | src/rpc/register.h | 2 | ||||
-rw-r--r-- | src/rpc/request.cpp | 4 | ||||
-rw-r--r-- | src/rpc/server.cpp | 37 | ||||
-rw-r--r-- | src/rpc/server.h | 6 | ||||
-rw-r--r-- | src/rpc/txoutproof.cpp | 5 | ||||
-rw-r--r-- | src/rpc/util.cpp | 47 | ||||
-rw-r--r-- | src/rpc/util.h | 9 |
17 files changed, 645 insertions, 316 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b449444aff..360f24ec55 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -22,6 +22,7 @@ #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> +#include <interfaces/mining.h> #include <kernel/coinstats.h> #include <logging/timer.h> #include <net.h> @@ -56,24 +57,32 @@ #include <condition_variable> #include <memory> #include <mutex> +#include <optional> using kernel::CCoinsStats; using kernel::CoinStatsHashType; +using interfaces::Mining; using node::BlockManager; using node::NodeContext; using node::SnapshotMetadata; using util::MakeUnorderedList; -struct CUpdatedBlock -{ - uint256 hash; - int height; -}; +std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*> +PrepareUTXOSnapshot( + Chainstate& chainstate, + const std::function<void()>& interruption_point = {}) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); -static GlobalMutex cs_blockchange; -static std::condition_variable cond_blockchange; -static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); +UniValue WriteUTXOSnapshot( + Chainstate& chainstate, + CCoinsViewCursor* pcursor, + CCoinsStats* maybe_stats, + const CBlockIndex* tip, + AutoFile& afile, + const fs::path& path, + const fs::path& temppath, + const std::function<void()>& interruption_point = {}); /* Calculate the difficulty for a given block index. */ @@ -184,8 +193,10 @@ UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIn case TxVerbosity::SHOW_DETAILS_AND_PREVOUT: CBlockUndo blockUndo; const bool is_not_pruned{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))}; - const bool have_undo{is_not_pruned && blockman.UndoReadFromDisk(blockUndo, blockindex)}; - + bool have_undo{is_not_pruned && WITH_LOCK(::cs_main, return blockindex.nStatus & BLOCK_HAVE_UNDO)}; + if (have_undo && !blockman.UndoReadFromDisk(blockUndo, blockindex)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event."); + } for (size_t i = 0; i < block.vtx.size(); ++i) { const CTransactionRef& tx = block.vtx.at(i); // coinbase transaction (i.e. i == 0) doesn't have undo data @@ -243,21 +254,12 @@ static RPCHelpMan getbestblockhash() }; } -void RPCNotifyBlockChange(const CBlockIndex* pindex) -{ - if(pindex) { - LOCK(cs_blockchange); - latestblock.hash = pindex->GetBlockHash(); - latestblock.height = pindex->nHeight; - } - cond_blockchange.notify_all(); -} - static RPCHelpMan waitfornewblock() { return RPCHelpMan{"waitfornewblock", - "\nWaits for a specific new block and returns useful info about it.\n" - "\nReturns the current block on timeout or exit.\n", + "\nWaits for any new block and returns useful info about it.\n" + "\nReturns the current block on timeout or exit.\n" + "\nMake sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", { {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, }, @@ -276,17 +278,16 @@ static RPCHelpMan waitfornewblock() int timeout = 0; if (!request.params[0].isNull()) timeout = request.params[0].getInt<int>(); + if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout"); - CUpdatedBlock block; - { - WAIT_LOCK(cs_blockchange, lock); - block = latestblock; - if(timeout) - cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); - else - cond_blockchange.wait(lock, [&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); }); - block = latestblock; + NodeContext& node = EnsureAnyNodeContext(request.context); + Mining& miner = EnsureMining(node); + + auto block{CHECK_NONFATAL(miner.getTip()).value()}; + if (IsRPCRunning()) { + block = timeout ? miner.waitTipChanged(block.hash, std::chrono::milliseconds(timeout)) : miner.waitTipChanged(block.hash); } + UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); @@ -299,7 +300,8 @@ static RPCHelpMan waitforblock() { return RPCHelpMan{"waitforblock", "\nWaits for a specific new block and returns useful info about it.\n" - "\nReturns the current block on timeout or exit.\n", + "\nReturns the current block on timeout or exit.\n" + "\nMake sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Block hash to wait for."}, {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, @@ -322,15 +324,22 @@ static RPCHelpMan waitforblock() if (!request.params[1].isNull()) timeout = request.params[1].getInt<int>(); + if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout"); - CUpdatedBlock block; - { - WAIT_LOCK(cs_blockchange, lock); - if(timeout) - cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning();}); - else - cond_blockchange.wait(lock, [&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.hash == hash || !IsRPCRunning(); }); - block = latestblock; + NodeContext& node = EnsureAnyNodeContext(request.context); + Mining& miner = EnsureMining(node); + + auto block{CHECK_NONFATAL(miner.getTip()).value()}; + const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout}; + while (IsRPCRunning() && block.hash != hash) { + if (timeout) { + auto now{std::chrono::steady_clock::now()}; + if (now >= deadline) break; + const MillisecondsDouble remaining{deadline - now}; + block = miner.waitTipChanged(block.hash, remaining); + } else { + block = miner.waitTipChanged(block.hash); + } } UniValue ret(UniValue::VOBJ); @@ -346,7 +355,8 @@ static RPCHelpMan waitforblockheight() return RPCHelpMan{"waitforblockheight", "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" - "\nReturns the current block on timeout or exit.\n", + "\nReturns the current block on timeout or exit.\n" + "\nMake sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "Block height to wait for."}, {"timeout", RPCArg::Type::NUM, RPCArg::Default{0}, "Time in milliseconds to wait for a response. 0 indicates no timeout."}, @@ -369,16 +379,25 @@ static RPCHelpMan waitforblockheight() if (!request.params[1].isNull()) timeout = request.params[1].getInt<int>(); + if (timeout < 0) throw JSONRPCError(RPC_MISC_ERROR, "Negative timeout"); - CUpdatedBlock block; - { - WAIT_LOCK(cs_blockchange, lock); - if(timeout) - cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning();}); - else - cond_blockchange.wait(lock, [&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {return latestblock.height >= height || !IsRPCRunning(); }); - block = latestblock; + NodeContext& node = EnsureAnyNodeContext(request.context); + Mining& miner = EnsureMining(node); + + auto block{CHECK_NONFATAL(miner.getTip()).value()}; + const auto deadline{std::chrono::steady_clock::now() + 1ms * timeout}; + + while (IsRPCRunning() && block.height < height) { + if (timeout) { + auto now{std::chrono::steady_clock::now()}; + if (now >= deadline) break; + const MillisecondsDouble remaining{deadline - now}; + block = miner.waitTipChanged(block.hash, remaining); + } else { + block = miner.waitTipChanged(block.hash); + } } + UniValue ret(UniValue::VOBJ); ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); @@ -580,20 +599,32 @@ static RPCHelpMan getblockheader() }; } +void CheckBlockDataAvailability(BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo) +{ + AssertLockHeld(cs_main); + uint32_t flag = check_for_undo ? BLOCK_HAVE_UNDO : BLOCK_HAVE_DATA; + if (!(blockindex.nStatus & flag)) { + if (blockman.IsBlockPruned(blockindex)) { + throw JSONRPCError(RPC_MISC_ERROR, strprintf("%s not available (pruned data)", check_for_undo ? "Undo data" : "Block")); + } + if (check_for_undo) { + throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available"); + } + throw JSONRPCError(RPC_MISC_ERROR, "Block not available (not fully downloaded)"); + } +} + static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex) { CBlock block; { LOCK(cs_main); - if (blockman.IsBlockPruned(blockindex)) { - throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); - } + CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false); } if (!blockman.ReadBlockFromDisk(block, blockindex)) { - // Block not found on disk. This could be because we have the block - // header in our index but not yet have the block or did not accept the - // block. Or if the block was pruned right after we released the lock above. + // Block not found on disk. This shouldn't normally happen unless the block was + // pruned right after we released the lock above. throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } @@ -606,16 +637,13 @@ static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBl FlatFilePos pos{}; { LOCK(cs_main); - if (blockman.IsBlockPruned(blockindex)) { - throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); - } + CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/false); pos = blockindex.GetBlockPos(); } if (!blockman.ReadRawBlockFromDisk(data, pos)) { - // Block not found on disk. This could be because we have the block - // header in our index but not yet have the block or did not accept the - // block. Or if the block was pruned right after we released the lock above. + // Block not found on disk. This shouldn't normally happen unless the block was + // pruned right after we released the lock above. throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk"); } @@ -631,9 +659,7 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& bloc { LOCK(cs_main); - if (blockman.IsBlockPruned(blockindex)) { - throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)"); - } + CheckBlockDataAvailability(blockman, blockindex, /*check_for_undo=*/true); } if (!blockman.UndoReadFromDisk(blockUndo, blockindex)) { @@ -740,14 +766,7 @@ static RPCHelpMan getblock() { uint256 hash(ParseHashV(request.params[0], "blockhash")); - int verbosity = 1; - if (!request.params[1].isNull()) { - if (request.params[1].isBool()) { - verbosity = request.params[1].get_bool() ? 1 : 0; - } else { - verbosity = request.params[1].getInt<int>(); - } - } + int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/1)}; const CBlockIndex* pblockindex; const CBlockIndex* tip; @@ -859,7 +878,7 @@ static RPCHelpMan pruneblockchain() } 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.\n"); + LogDebug(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } @@ -1577,6 +1596,27 @@ static RPCHelpMan preciousblock() }; } +void InvalidateBlock(ChainstateManager& chainman, const uint256 block_hash) { + BlockValidationState state; + CBlockIndex* pblockindex; + { + LOCK(chainman.GetMutex()); + pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash); + if (!pblockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + } + chainman.ActiveChainstate().InvalidateBlock(state, pblockindex); + + if (state.IsValid()) { + chainman.ActiveChainstate().ActivateBestChain(state); + } + + if (!state.IsValid()) { + throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); + } +} + static RPCHelpMan invalidateblock() { return RPCHelpMan{"invalidateblock", @@ -1591,31 +1631,33 @@ static RPCHelpMan invalidateblock() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + ChainstateManager& chainman = EnsureAnyChainman(request.context); uint256 hash(ParseHashV(request.params[0], "blockhash")); - BlockValidationState state; - ChainstateManager& chainman = EnsureAnyChainman(request.context); - CBlockIndex* pblockindex; + InvalidateBlock(chainman, hash); + + return UniValue::VNULL; +}, + }; +} + +void ReconsiderBlock(ChainstateManager& chainman, uint256 block_hash) { { - LOCK(cs_main); - pblockindex = chainman.m_blockman.LookupBlockIndex(hash); + LOCK(chainman.GetMutex()); + CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash); if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - } - chainman.ActiveChainstate().InvalidateBlock(state, pblockindex); - if (state.IsValid()) { - chainman.ActiveChainstate().ActivateBestChain(state); + chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex); } + BlockValidationState state; + chainman.ActiveChainstate().ActivateBestChain(state); + if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - - return UniValue::VNULL; -}, - }; } static RPCHelpMan reconsiderblock() @@ -1636,22 +1678,7 @@ static RPCHelpMan reconsiderblock() ChainstateManager& chainman = EnsureAnyChainman(request.context); uint256 hash(ParseHashV(request.params[0], "blockhash")); - { - LOCK(cs_main); - CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(hash); - if (!pblockindex) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - } - - chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex); - } - - BlockValidationState state; - chainman.ActiveChainstate().ActivateBestChain(state); - - if (!state.IsValid()) { - throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); - } + ReconsiderBlock(chainman, hash); return UniValue::VNULL; }, @@ -2641,6 +2668,42 @@ static RPCHelpMan getblockfilter() } /** + * RAII class that disables the network in its constructor and enables it in its + * destructor. + */ +class NetworkDisable +{ + CConnman& m_connman; +public: + NetworkDisable(CConnman& connman) : m_connman(connman) { + m_connman.SetNetworkActive(false); + if (m_connman.GetNetworkActive()) { + throw JSONRPCError(RPC_MISC_ERROR, "Network activity could not be suspended."); + } + }; + ~NetworkDisable() { + m_connman.SetNetworkActive(true); + }; +}; + +/** + * RAII class that temporarily rolls back the local chain in it's constructor + * and rolls it forward again in it's destructor. + */ +class TemporaryRollback +{ + ChainstateManager& m_chainman; + const CBlockIndex& m_invalidate_index; +public: + TemporaryRollback(ChainstateManager& chainman, const CBlockIndex& index) : m_chainman(chainman), m_invalidate_index(index) { + InvalidateBlock(m_chainman, m_invalidate_index.GetBlockHash()); + }; + ~TemporaryRollback() { + ReconsiderBlock(m_chainman, m_invalidate_index.GetBlockHash()); + }; +}; + +/** * Serialize the UTXO set to a file for loading elsewhere. * * @see SnapshotMetadata @@ -2649,9 +2712,20 @@ static RPCHelpMan dumptxoutset() { return RPCHelpMan{ "dumptxoutset", - "Write the serialized UTXO set to a file.", + "Write the serialized UTXO set to a file. This can be used in loadtxoutset afterwards if this snapshot height is supported in the chainparams as well.\n\n" + "Unless the the \"latest\" type is requested, the node will roll back to the requested height and network activity will be suspended during this process. " + "Because of this it is discouraged to interact with the node in any other way during the execution of this call to avoid inconsistent results and race conditions, particularly RPCs that interact with blockstorage.\n\n" + "This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)", { {"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."}, + {"type", RPCArg::Type::STR, RPCArg::Default(""), "The type of snapshot to create. Can be \"latest\" to create a snapshot of the current UTXO set or \"rollback\" to temporarily roll back the state of the node to a historical block before creating the snapshot of a historical UTXO set. This parameter can be omitted if a separate \"rollback\" named parameter is specified indicating the height or hash of a specific historical block. If \"rollback\" is specified and separate \"rollback\" named parameter is not specified, this will roll back to the latest valid snapshot block that can currently be loaded with loadtxoutset."}, + {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", + { + {"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, + "Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case.", + RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}}, + }, + }, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2665,10 +2739,33 @@ static RPCHelpMan dumptxoutset() } }, RPCExamples{ - HelpExampleCli("dumptxoutset", "utxo.dat") + HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") + + HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") + + HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + NodeContext& node = EnsureAnyNodeContext(request.context); + const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())}; + const CBlockIndex* target_index{nullptr}; + const std::string snapshot_type{self.Arg<std::string>("type")}; + const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]}; + if (options.exists("rollback")) { + if (!snapshot_type.empty() && snapshot_type != "rollback") { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type)); + } + target_index = ParseHashOrHeight(options["rollback"], *node.chainman); + } else if (snapshot_type == "rollback") { + auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights(); + CHECK_NONFATAL(snapshot_heights.size() > 0); + auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end()); + target_index = ParseHashOrHeight(*max_height, *node.chainman); + } else if (snapshot_type == "latest") { + target_index = tip; + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type)); + } + const ArgsManager& args{EnsureAnyArgsman(request.context)}; const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str())); // Write to a temporary path and then move into `path` on completion @@ -2690,9 +2787,68 @@ static RPCHelpMan dumptxoutset() "Couldn't open file " + temppath.utf8string() + " for writing."); } - NodeContext& node = EnsureAnyNodeContext(request.context); - UniValue result = CreateUTXOSnapshot( - node, node.chainman->ActiveChainstate(), afile, path, temppath); + CConnman& connman = EnsureConnman(node); + const CBlockIndex* invalidate_index{nullptr}; + std::optional<NetworkDisable> disable_network; + std::optional<TemporaryRollback> temporary_rollback; + + // If the user wants to dump the txoutset of the current tip, we don't have + // to roll back at all + if (target_index != tip) { + // If the node is running in pruned mode we ensure all necessary block + // data is available before starting to roll back. + if (node.chainman->m_blockman.IsPruneMode()) { + LOCK(node.chainman->GetMutex()); + const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()}; + const CBlockIndex* first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)}; + if (first_block->nHeight > target_index->nHeight) { + throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned."); + } + } + + // Suspend network activity for the duration of the process when we are + // rolling back the chain to get a utxo set from a past height. We do + // this so we don't punish peers that send us that send us data that + // seems wrong in this temporary state. For example a normal new block + // would be classified as a block connecting an invalid block. + // Skip if the network is already disabled because this + // automatically re-enables the network activity at the end of the + // process which may not be what the user wants. + if (connman.GetNetworkActive()) { + disable_network.emplace(connman); + } + + invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(target_index)); + temporary_rollback.emplace(*node.chainman, *invalidate_index); + } + + Chainstate* chainstate; + std::unique_ptr<CCoinsViewCursor> cursor; + CCoinsStats stats; + { + // Lock the chainstate before calling PrepareUtxoSnapshot, to be able + // to get a UTXO database cursor while the chain is pointing at the + // target block. After that, release the lock while calling + // WriteUTXOSnapshot. The cursor will remain valid and be used by + // WriteUTXOSnapshot to write a consistent snapshot even if the + // chainstate changes. + LOCK(node.chainman->GetMutex()); + chainstate = &node.chainman->ActiveChainstate(); + // In case there is any issue with a block being read from disk we need + // to stop here, otherwise the dump could still be created for the wrong + // height. + // The new tip could also not be the target block if we have a stale + // sister block of invalidate_index. This block (or a descendant) would + // be activated as the new tip and we would not get to new_tip_index. + if (target_index != chainstate->m_chain.Tip()) { + LogWarning("dumptxoutset failed to roll back to requested height, reverting to tip.\n"); + throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height."); + } else { + std::tie(cursor, stats, tip) = PrepareUTXOSnapshot(*chainstate, node.rpc_interruption_point); + } + } + + UniValue result = WriteUTXOSnapshot(*chainstate, cursor.get(), &stats, tip, afile, path, temppath, node.rpc_interruption_point); fs::rename(temppath, path); result.pushKV("path", path.utf8string()); @@ -2701,12 +2857,10 @@ static RPCHelpMan dumptxoutset() }; } -UniValue CreateUTXOSnapshot( - NodeContext& node, +std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*> +PrepareUTXOSnapshot( Chainstate& chainstate, - AutoFile& afile, - const fs::path& path, - const fs::path& temppath) + const std::function<void()>& interruption_point) { std::unique_ptr<CCoinsViewCursor> pcursor; std::optional<CCoinsStats> maybe_stats; @@ -2716,7 +2870,7 @@ UniValue CreateUTXOSnapshot( // We need to lock cs_main to ensure that the coinsdb isn't written to // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats // based upon the coinsdb, and (iii) constructing a cursor to the - // coinsdb for use below this block. + // coinsdb for use in WriteUTXOSnapshot. // // Cursors returned by leveldb iterate over snapshots, so the contents // of the pcursor will not be affected by simultaneous writes during @@ -2725,11 +2879,11 @@ UniValue CreateUTXOSnapshot( // See discussion here: // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369 // - LOCK(::cs_main); + AssertLockHeld(::cs_main); chainstate.ForceFlushStateToDisk(); - maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point); + maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point); if (!maybe_stats) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } @@ -2738,6 +2892,19 @@ UniValue CreateUTXOSnapshot( tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock)); } + return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip}; +} + +UniValue WriteUTXOSnapshot( + Chainstate& chainstate, + CCoinsViewCursor* pcursor, + CCoinsStats* maybe_stats, + const CBlockIndex* tip, + AutoFile& afile, + const fs::path& path, + const fs::path& temppath, + const std::function<void()>& interruption_point) +{ LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", tip->nHeight, tip->GetBlockHash().ToString(), fs::PathToString(path), fs::PathToString(temppath))); @@ -2773,7 +2940,7 @@ UniValue CreateUTXOSnapshot( pcursor->GetKey(key); last_hash = key.hash; while (pcursor->Valid()) { - if (iter % 5000 == 0) node.rpc_interruption_point(); + if (iter % 5000 == 0) interruption_point(); ++iter; if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { if (key.hash != last_hash) { @@ -2804,6 +2971,17 @@ UniValue CreateUTXOSnapshot( return result; } +UniValue CreateUTXOSnapshot( + node::NodeContext& node, + Chainstate& chainstate, + AutoFile& afile, + const fs::path& path, + const fs::path& tmppath) +{ + auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))}; + return WriteUTXOSnapshot(chainstate, cursor.get(), &stats, tip, afile, path, tmppath, node.rpc_interruption_point); +} + static RPCHelpMan loadtxoutset() { return RPCHelpMan{ @@ -2838,7 +3016,7 @@ static RPCHelpMan loadtxoutset() } }, RPCExamples{ - HelpExampleCli("loadtxoutset", "utxo.dat") + HelpExampleCli("-rpcclienttimeout=0 loadtxoutset", "utxo.dat") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { @@ -2866,6 +3044,13 @@ static RPCHelpMan loadtxoutset() throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string())); } + // Because we can't provide historical blocks during tip or background sync. + // Update local services to reflect we are a limited peer until we are fully sync. + node.connman->RemoveLocalServices(NODE_NETWORK); + // Setting the limited state is usually redundant because the node can always + // provide the last 288 blocks, but it doesn't hurt to set it. + node.connman->AddLocalServices(NODE_NETWORK_LIMITED); + CBlockIndex& snapshot_index{*CHECK_NONFATAL(*activation_result)}; UniValue result(UniValue::VOBJ); diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index f6a7fe236c..89b9921d55 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -35,9 +35,6 @@ static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; */ double GetDifficulty(const CBlockIndex& blockindex); -/** Callback for when block tip changed. */ -void RPCNotifyBlockChange(const CBlockIndex*); - /** Block description to JSON */ UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex& tip, const CBlockIndex& blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); @@ -48,7 +45,7 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); /** - * Helper to create UTXO snapshots given a chainstate and a file handle. + * Test-only helper to create UTXO snapshots given a chainstate and a file handle. * @return a UniValue map containing metadata about the snapshot. */ UniValue CreateUTXOSnapshot( @@ -60,5 +57,6 @@ UniValue CreateUTXOSnapshot( //! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned std::optional<int> GetPruneHeight(const node::BlockManager& blockman, const CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); +void CheckBlockDataAvailability(node::BlockManager& blockman, const CBlockIndex& blockindex, bool check_for_undo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); #endif // BITCOIN_RPC_BLOCKCHAIN_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b866fa484b..601e4fa7bf 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -187,6 +187,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "gettxoutproof", 0, "txids" }, { "gettxoutsetinfo", 1, "hash_or_height" }, { "gettxoutsetinfo", 2, "use_index"}, + { "dumptxoutset", 2, "options" }, + { "dumptxoutset", 2, "rollback" }, { "lockunspent", 0, "unlock" }, { "lockunspent", 1, "transactions" }, { "lockunspent", 2, "persistent" }, @@ -252,6 +254,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "keypoolrefill", 0, "newsize" }, { "getrawmempool", 0, "verbose" }, { "getrawmempool", 1, "mempool_sequence" }, + { "getorphantxs", 0, "verbosity" }, + { "getorphantxs", 0, "verbose" }, { "estimatesmartfee", 0, "conf_target" }, { "estimaterawfee", 0, "conf_target" }, { "estimaterawfee", 1, "threshold" }, diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 3ad7a940e0..44de5443fa 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <common/args.h> #include <common/system.h> diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index d61898260b..27a00c5d91 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -8,8 +8,10 @@ #include <node/mempool_persist.h> #include <chainparams.h> +#include <consensus/validation.h> #include <core_io.h> #include <kernel/mempool_entry.h> +#include <net_processing.h> #include <node/mempool_persist_args.h> #include <node/types.h> #include <policy/rbf.h> @@ -24,6 +26,7 @@ #include <util/moneystr.h> #include <util/strencodings.h> #include <util/time.h> +#include <util/vector.h> #include <utility> @@ -812,6 +815,104 @@ static RPCHelpMan savemempool() }; } +static std::vector<RPCResult> OrphanDescription() +{ + return { + RPCResult{RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, + RPCResult{RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"}, + RPCResult{RPCResult::Type::NUM, "bytes", "The serialized transaction size in bytes"}, + RPCResult{RPCResult::Type::NUM, "vsize", "The virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."}, + RPCResult{RPCResult::Type::NUM, "weight", "The transaction weight as defined in BIP 141."}, + RPCResult{RPCResult::Type::NUM_TIME, "expiration", "The orphan expiration time expressed in " + UNIX_EPOCH_TIME}, + RPCResult{RPCResult::Type::ARR, "from", "", + { + RPCResult{RPCResult::Type::NUM, "peer_id", "Peer ID"}, + }}, + }; +} + +static UniValue OrphanToJSON(const TxOrphanage::OrphanTxBase& orphan) +{ + UniValue o(UniValue::VOBJ); + o.pushKV("txid", orphan.tx->GetHash().ToString()); + o.pushKV("wtxid", orphan.tx->GetWitnessHash().ToString()); + o.pushKV("bytes", orphan.tx->GetTotalSize()); + o.pushKV("vsize", GetVirtualTransactionSize(*orphan.tx)); + o.pushKV("weight", GetTransactionWeight(*orphan.tx)); + o.pushKV("expiration", int64_t{TicksSinceEpoch<std::chrono::seconds>(orphan.nTimeExpire)}); + UniValue from(UniValue::VARR); + from.push_back(orphan.fromPeer); // only one fromPeer for now + o.pushKV("from", from); + return o; +} + +static RPCHelpMan getorphantxs() +{ + return RPCHelpMan{"getorphantxs", + "\nShows transactions in the tx orphanage.\n" + "\nEXPERIMENTAL warning: this call may be changed in future releases.\n", + { + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for an array of txids (may contain duplicates), 1 for an array of objects with tx details, and 2 for details from (1) and tx hex", + RPCArgOptions{.skip_type_check = true}}, + }, + { + RPCResult{"for verbose = 0", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, + }}, + RPCResult{"for verbose = 1", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", OrphanDescription()}, + }}, + RPCResult{"for verbose = 2", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + Cat<std::vector<RPCResult>>( + OrphanDescription(), + {{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded transaction data"}} + ) + }, + }}, + }, + RPCExamples{ + HelpExampleCli("getorphantxs", "2") + + HelpExampleRpc("getorphantxs", "2") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + const NodeContext& node = EnsureAnyNodeContext(request.context); + PeerManager& peerman = EnsurePeerman(node); + std::vector<TxOrphanage::OrphanTxBase> orphanage = peerman.GetOrphanTransactions(); + + int verbosity{ParseVerbosity(request.params[0], /*default_verbosity=*/0)}; + + UniValue ret(UniValue::VARR); + + if (verbosity <= 0) { + for (auto const& orphan : orphanage) { + ret.push_back(orphan.tx->GetHash().ToString()); + } + } else if (verbosity == 1) { + for (auto const& orphan : orphanage) { + ret.push_back(OrphanToJSON(orphan)); + } + } else { + // >= 2 + for (auto const& orphan : orphanage) { + UniValue o{OrphanToJSON(orphan)}; + o.pushKV("hex", EncodeHexTx(*orphan.tx)); + ret.push_back(o); + } + } + + return ret; + }, + }; +} + static RPCHelpMan submitpackage() { return RPCHelpMan{"submitpackage", @@ -1027,6 +1128,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &getrawmempool}, {"blockchain", &importmempool}, {"blockchain", &savemempool}, + {"hidden", &getorphantxs}, {"rawtransactions", &submitpackage}, }; for (const auto& c : commands) { diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 3c41e136ec..44605cbc89 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <chain.h> #include <chainparams.h> @@ -45,9 +45,9 @@ #include <memory> #include <stdint.h> -using node::BlockAssembler; -using node::CBlockTemplate; +using interfaces::BlockTemplate; using interfaces::Mining; +using node::BlockAssembler; using node::NodeContext; using node::RegenerateCommitments; using node::UpdateTime; @@ -130,7 +130,7 @@ static RPCHelpMan getnetworkhashps() }; } -static bool GenerateBlock(ChainstateManager& chainman, Mining& miner, CBlock& block, uint64_t& max_tries, std::shared_ptr<const CBlock>& block_out, bool process_new_block) +static bool GenerateBlock(ChainstateManager& chainman, Mining& miner, CBlock&& block, uint64_t& max_tries, std::shared_ptr<const CBlock>& block_out, bool process_new_block) { block_out.reset(); block.hashMerkleRoot = BlockMerkleRoot(block); @@ -146,7 +146,7 @@ static bool GenerateBlock(ChainstateManager& chainman, Mining& miner, CBlock& bl return true; } - block_out = std::make_shared<const CBlock>(block); + block_out = std::make_shared<const CBlock>(std::move(block)); if (!process_new_block) return true; @@ -161,12 +161,11 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const { UniValue blockHashes(UniValue::VARR); while (nGenerate > 0 && !chainman.m_interrupt) { - std::unique_ptr<CBlockTemplate> pblocktemplate(miner.createNewBlock(coinbase_script)); - if (!pblocktemplate.get()) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); + std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock(coinbase_script)); + CHECK_NONFATAL(block_template); std::shared_ptr<const CBlock> block_out; - if (!GenerateBlock(chainman, miner, pblocktemplate->block, nMaxTries, block_out, /*process_new_block=*/true)) { + if (!GenerateBlock(chainman, miner, block_template->getBlock(), nMaxTries, block_out, /*process_new_block=*/true)) { break; } @@ -181,35 +180,36 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const static bool getScriptFromDescriptor(const std::string& descriptor, CScript& script, std::string& error) { FlatSigningProvider key_provider; - const auto desc = Parse(descriptor, key_provider, error, /* require_checksum = */ false); - if (desc) { - if (desc->IsRange()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?"); - } - - FlatSigningProvider provider; - std::vector<CScript> scripts; - if (!desc->Expand(0, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); - } + const auto descs = Parse(descriptor, key_provider, error, /* require_checksum = */ false); + if (descs.empty()) return false; + if (descs.size() > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptor not accepted"); + } + const auto& desc = descs.at(0); + if (desc->IsRange()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptor not accepted. Maybe pass through deriveaddresses first?"); + } - // Combo descriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1 - CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 4); + FlatSigningProvider provider; + std::vector<CScript> scripts; + if (!desc->Expand(0, key_provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); + } - if (scripts.size() == 1) { - script = scripts.at(0); - } else if (scripts.size() == 4) { - // For uncompressed keys, take the 3rd script, since it is p2wpkh - script = scripts.at(2); - } else { - // Else take the 2nd script, since it is p2pkh - script = scripts.at(1); - } + // Combo descriptors can have 2 or 4 scripts, so we can't just check scripts.size() == 1 + CHECK_NONFATAL(scripts.size() > 0 && scripts.size() <= 4); - return true; + if (scripts.size() == 1) { + script = scripts.at(0); + } else if (scripts.size() == 4) { + // For uncompressed keys, take the 3rd script, since it is p2wpkh + script = scripts.at(2); } else { - return false; + // Else take the 2nd script, since it is p2pkh + script = scripts.at(1); } + + return true; } static RPCHelpMan generatetodescriptor() @@ -370,11 +370,10 @@ static RPCHelpMan generateblock() ChainstateManager& chainman = EnsureChainman(node); { - std::unique_ptr<CBlockTemplate> blocktemplate{miner.createNewBlock(coinbase_script, {.use_mempool = false})}; - if (!blocktemplate) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); - } - block = blocktemplate->block; + std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock(coinbase_script, {.use_mempool = false})}; + CHECK_NONFATAL(block_template); + + block = block_template->getBlock(); } CHECK_NONFATAL(block.vtx.size() == 1); @@ -393,7 +392,7 @@ static RPCHelpMan generateblock() std::shared_ptr<const CBlock> block_out; uint64_t max_tries{DEFAULT_MAX_TRIES}; - if (!GenerateBlock(chainman, miner, block, max_tries, block_out, process_new_block) || !block_out) { + if (!GenerateBlock(chainman, miner, std::move(block), max_tries, block_out, process_new_block) || !block_out) { throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block."); } @@ -662,7 +661,7 @@ static RPCHelpMan getblocktemplate() ChainstateManager& chainman = EnsureChainman(node); Mining& miner = EnsureMining(node); LOCK(cs_main); - uint256 tip{CHECK_NONFATAL(miner.getTipHash()).value()}; + uint256 tip{CHECK_NONFATAL(miner.getTip()).value().hash}; std::string strMode = "template"; UniValue lpval = NullUniValue; @@ -739,7 +738,6 @@ static RPCHelpMan getblocktemplate() { // Wait to respond until either the best block changes, OR a minute has passed and there are more transactions uint256 hashWatchedChain; - std::chrono::steady_clock::time_point checktxtime; unsigned int nTransactionsUpdatedLastLP; if (lpval.isStr()) @@ -760,24 +758,19 @@ static RPCHelpMan getblocktemplate() // Release lock while waiting LEAVE_CRITICAL_SECTION(cs_main); { - checktxtime = std::chrono::steady_clock::now() + std::chrono::minutes(1); - - WAIT_LOCK(g_best_block_mutex, lock); - while (g_best_block == hashWatchedChain && IsRPCRunning()) - { - if (g_best_block_cv.wait_until(lock, checktxtime) == std::cv_status::timeout) - { - // Timeout: Check transactions for update - // without holding the mempool lock to avoid deadlocks - if (miner.getTransactionsUpdated() != nTransactionsUpdatedLastLP) - break; - checktxtime += std::chrono::seconds(10); - } + MillisecondsDouble checktxtime{std::chrono::minutes(1)}; + while (tip == hashWatchedChain && IsRPCRunning()) { + tip = miner.waitTipChanged(hashWatchedChain, checktxtime).hash; + // Timeout: Check transactions for update + // without holding the mempool lock to avoid deadlocks + if (miner.getTransactionsUpdated() != nTransactionsUpdatedLastLP) + break; + checktxtime = std::chrono::seconds(10); } } ENTER_CRITICAL_SECTION(cs_main); - tip = CHECK_NONFATAL(miner.getTipHash()).value(); + tip = CHECK_NONFATAL(miner.getTip()).value().hash; if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); @@ -799,7 +792,7 @@ static RPCHelpMan getblocktemplate() // Update block static CBlockIndex* pindexPrev; static int64_t time_start; - static std::unique_ptr<CBlockTemplate> pblocktemplate; + static std::unique_ptr<BlockTemplate> block_template; if (!pindexPrev || pindexPrev->GetBlockHash() != tip || (miner.getTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5)) { @@ -813,20 +806,19 @@ static RPCHelpMan getblocktemplate() // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = miner.createNewBlock(scriptDummy); - if (!pblocktemplate) { - throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); - } + block_template = miner.createNewBlock(scriptDummy); + CHECK_NONFATAL(block_template); + // Need to update only after we know createNewBlock succeeded pindexPrev = pindexPrevNew; } CHECK_NONFATAL(pindexPrev); - CBlock* pblock = &pblocktemplate->block; // pointer for convenience + CBlock block{block_template->getBlock()}; // Update nTime - UpdateTime(pblock, consensusParams, pindexPrev); - pblock->nNonce = 0; + UpdateTime(&block, consensusParams, pindexPrev); + block.nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT); @@ -835,8 +827,11 @@ static RPCHelpMan getblocktemplate() UniValue transactions(UniValue::VARR); std::map<uint256, int64_t> setTxIndex; + std::vector<CAmount> tx_fees{block_template->getTxFees()}; + std::vector<CAmount> tx_sigops{block_template->getTxSigops()}; + int i = 0; - for (const auto& it : pblock->vtx) { + for (const auto& it : block.vtx) { const CTransaction& tx = *it; uint256 txHash = tx.GetHash(); setTxIndex[txHash] = i++; @@ -859,8 +854,8 @@ static RPCHelpMan getblocktemplate() entry.pushKV("depends", std::move(deps)); int index_in_template = i - 1; - entry.pushKV("fee", pblocktemplate->vTxFees[index_in_template]); - int64_t nTxSigOps = pblocktemplate->vTxSigOpsCost[index_in_template]; + entry.pushKV("fee", tx_fees.at(index_in_template)); + int64_t nTxSigOps{tx_sigops.at(index_in_template)}; if (fPreSegWit) { CHECK_NONFATAL(nTxSigOps % WITNESS_SCALE_FACTOR == 0); nTxSigOps /= WITNESS_SCALE_FACTOR; @@ -873,7 +868,7 @@ static RPCHelpMan getblocktemplate() UniValue aux(UniValue::VOBJ); - arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits); + arith_uint256 hashTarget = arith_uint256().SetCompact(block.nBits); UniValue aMutable(UniValue::VARR); aMutable.push_back("time"); @@ -903,7 +898,7 @@ static RPCHelpMan getblocktemplate() break; case ThresholdState::LOCKED_IN: // Ensure bit is set in block version - pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); + block.nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); [[fallthrough]]; case ThresholdState::STARTED: { @@ -912,7 +907,7 @@ static RPCHelpMan getblocktemplate() if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (!vbinfo.gbt_force) { // If the client doesn't support this, don't indicate it in the [default] version - pblock->nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); + block.nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); } } break; @@ -932,15 +927,15 @@ static RPCHelpMan getblocktemplate() } } } - result.pushKV("version", pblock->nVersion); + result.pushKV("version", block.nVersion); result.pushKV("rules", std::move(aRules)); result.pushKV("vbavailable", std::move(vbavailable)); result.pushKV("vbrequired", int(0)); - result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); + result.pushKV("previousblockhash", block.hashPrevBlock.GetHex()); result.pushKV("transactions", std::move(transactions)); result.pushKV("coinbaseaux", std::move(aux)); - result.pushKV("coinbasevalue", (int64_t)pblock->vtx[0]->vout[0].nValue); + result.pushKV("coinbasevalue", (int64_t)block.vtx[0]->vout[0].nValue); result.pushKV("longpollid", tip.GetHex() + ToString(nTransactionsUpdatedLast)); result.pushKV("target", hashTarget.GetHex()); result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1); @@ -959,16 +954,16 @@ static RPCHelpMan getblocktemplate() if (!fPreSegWit) { result.pushKV("weightlimit", (int64_t)MAX_BLOCK_WEIGHT); } - result.pushKV("curtime", pblock->GetBlockTime()); - result.pushKV("bits", strprintf("%08x", pblock->nBits)); + result.pushKV("curtime", block.GetBlockTime()); + result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); if (consensusParams.signet_blocks) { result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge)); } - if (!pblocktemplate->vchCoinbaseCommitment.empty()) { - result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment)); + if (!block_template->getCoinbaseCommitment().empty()) { + result.pushKV("default_witness_commitment", HexStr(block_template->getCoinbaseCommitment())); } return result; @@ -1095,7 +1090,7 @@ static RPCHelpMan submitheader() } BlockValidationState state; - chainman.ProcessNewBlockHeaders({h}, /*min_pow_checked=*/true, state); + chainman.ProcessNewBlockHeaders({{h}}, /*min_pow_checked=*/true, state); if (state.IsValid()) return UniValue::VNULL; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1119a3e668..5398685074 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -129,8 +129,8 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"}, {RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"}, {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"}, - {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n" - "peer selection (only available if the asmap config flag is set)"}, + {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying\n" + "peer selection (only displayed if the -asmap config option is set)"}, {RPCResult::Type::STR_HEX, "services", "The services offered"}, {RPCResult::Type::ARR, "servicesnames", "the services offered, in human-readable form", { @@ -1102,12 +1102,12 @@ static RPCHelpMan getaddrmaninfo() }; } -UniValue AddrmanEntryToJSON(const AddrInfo& info, CConnman& connman) +UniValue AddrmanEntryToJSON(const AddrInfo& info, const CConnman& connman) { UniValue ret(UniValue::VOBJ); ret.pushKV("address", info.ToStringAddr()); - const auto mapped_as{connman.GetMappedAS(info)}; - if (mapped_as != 0) { + const uint32_t mapped_as{connman.GetMappedAS(info)}; + if (mapped_as) { ret.pushKV("mapped_as", mapped_as); } ret.pushKV("port", info.GetPort()); @@ -1116,14 +1116,14 @@ UniValue AddrmanEntryToJSON(const AddrInfo& info, CConnman& connman) ret.pushKV("network", GetNetworkName(info.GetNetClass())); ret.pushKV("source", info.source.ToStringAddr()); ret.pushKV("source_network", GetNetworkName(info.source.GetNetClass())); - const auto source_mapped_as{connman.GetMappedAS(info.source)}; - if (source_mapped_as != 0) { + const uint32_t source_mapped_as{connman.GetMappedAS(info.source)}; + if (source_mapped_as) { ret.pushKV("source_mapped_as", source_mapped_as); } return ret; } -UniValue AddrmanTableToJSON(const std::vector<std::pair<AddrInfo, AddressPosition>>& tableInfos, CConnman& connman) +UniValue AddrmanTableToJSON(const std::vector<std::pair<AddrInfo, AddressPosition>>& tableInfos, const CConnman& connman) { UniValue table(UniValue::VOBJ); for (const auto& e : tableInfos) { @@ -1150,14 +1150,14 @@ static RPCHelpMan getrawaddrman() {RPCResult::Type::OBJ_DYN, "table", "buckets with addresses in the address manager table ( new, tried )", { {RPCResult::Type::OBJ, "bucket/position", "the location in the address manager table (<bucket>/<position>)", { {RPCResult::Type::STR, "address", "The address of the node"}, - {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The ASN mapped to the IP of this peer per our current ASMap"}, + {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the peer, used for diversifying peer selection (only displayed if the -asmap config option is set)"}, {RPCResult::Type::NUM, "port", "The port number of the node"}, {RPCResult::Type::STR, "network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the address"}, {RPCResult::Type::NUM, "services", "The services offered by the node"}, {RPCResult::Type::NUM_TIME, "time", "The " + UNIX_EPOCH_TIME + " when the node was last seen"}, {RPCResult::Type::STR, "source", "The address that relayed the address to us"}, {RPCResult::Type::STR, "source_network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the source address"}, - {RPCResult::Type::NUM, "source_mapped_as", /*optional=*/true, "The ASN mapped to the IP of this peer's source per our current ASMap"} + {RPCResult::Type::NUM, "source_mapped_as", /*optional=*/true, "Mapped AS (Autonomous System) number at the end of the BGP route to the source, used for diversifying peer selection (only displayed if the -asmap config option is set)"} }} }} } diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index 54e2c8e226..5e36273cf4 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <chainparams.h> #include <httpserver.h> @@ -244,15 +244,15 @@ static RPCHelpMan logging() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - uint32_t original_log_categories = LogInstance().GetCategoryMask(); + BCLog::CategoryMask original_log_categories = LogInstance().GetCategoryMask(); if (request.params[0].isArray()) { EnableOrDisableLogCategories(request.params[0], true); } if (request.params[1].isArray()) { EnableOrDisableLogCategories(request.params[1], false); } - uint32_t updated_log_categories = LogInstance().GetCategoryMask(); - uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; + BCLog::CategoryMask updated_log_categories = LogInstance().GetCategoryMask(); + BCLog::CategoryMask changed_log_categories = original_log_categories ^ updated_log_categories; // Update libevent logging if BCLog::LIBEVENT has changed. if (changed_log_categories & BCLog::LIBEVENT) { diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 01a9e59284..49f3a81243 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -175,7 +175,11 @@ static RPCHelpMan getdescriptorinfo() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"}, + {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys. For a multipath descriptor, only the first will be returned."}, + {RPCResult::Type::ARR, "multipath_expansion", /*optional=*/true, "All descriptors produced by expanding multipath derivation elements. Only if the provided descriptor specifies multipath derivation elements.", + { + {RPCResult::Type::STR, "", ""}, + }}, {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"}, {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"}, {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"}, @@ -191,22 +195,65 @@ static RPCHelpMan getdescriptorinfo() { FlatSigningProvider provider; std::string error; - auto desc = Parse(request.params[0].get_str(), provider, error); - if (!desc) { + auto descs = Parse(request.params[0].get_str(), provider, error); + if (descs.empty()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } UniValue result(UniValue::VOBJ); - result.pushKV("descriptor", desc->ToString()); + result.pushKV("descriptor", descs.at(0)->ToString()); + + if (descs.size() > 1) { + UniValue multipath_descs(UniValue::VARR); + for (const auto& d : descs) { + multipath_descs.push_back(d->ToString()); + } + result.pushKV("multipath_expansion", multipath_descs); + } + result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); - result.pushKV("isrange", desc->IsRange()); - result.pushKV("issolvable", desc->IsSolvable()); + result.pushKV("isrange", descs.at(0)->IsRange()); + result.pushKV("issolvable", descs.at(0)->IsSolvable()); result.pushKV("hasprivatekeys", provider.keys.size() > 0); return result; }, }; } +static UniValue DeriveAddresses(const Descriptor* desc, int64_t range_begin, int64_t range_end, FlatSigningProvider& key_provider) +{ + UniValue addresses(UniValue::VARR); + + for (int64_t i = range_begin; i <= range_end; ++i) { + FlatSigningProvider provider; + std::vector<CScript> scripts; + if (!desc->Expand(i, key_provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); + } + + for (const CScript& script : scripts) { + CTxDestination dest; + if (!ExtractDestination(script, dest)) { + // ExtractDestination no longer returns true for P2PK since it doesn't have a corresponding address + // However combo will output P2PK and should just ignore that script + if (scripts.size() > 1 && std::get_if<PubKeyDestination>(&dest)) { + continue; + } + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); + } + + addresses.push_back(EncodeDestination(dest)); + } + } + + // This should not be possible, but an assert seems overkill: + if (addresses.empty()) { + throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); + } + + return addresses; +} + static RPCHelpMan deriveaddresses() { const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; @@ -226,11 +273,24 @@ static RPCHelpMan deriveaddresses() {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, }, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR, "address", "the derived addresses"}, - } + { + RPCResult{"for single derivation descriptors", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR, "address", "the derived addresses"}, + } + }, + RPCResult{"for multipath descriptors", + RPCResult::Type::ARR, "", "The derived addresses for each of the multipath expansions of the descriptor, in multipath specifier order", + { + { + RPCResult::Type::ARR, "", "The derived addresses for a multipath descriptor expansion", + { + {RPCResult::Type::STR, "address", "the derived address"}, + }, + }, + }, + }, }, RPCExamples{ "First three native segwit receive addresses\n" + @@ -250,11 +310,11 @@ static RPCHelpMan deriveaddresses() FlatSigningProvider key_provider; std::string error; - auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); - if (!desc) { + auto descs = Parse(desc_str, key_provider, error, /* require_checksum = */ true); + if (descs.empty()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } - + auto& desc = descs.at(0); if (!desc->IsRange() && request.params.size() > 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); } @@ -263,36 +323,18 @@ static RPCHelpMan deriveaddresses() throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor"); } - UniValue addresses(UniValue::VARR); - - for (int64_t i = range_begin; i <= range_end; ++i) { - FlatSigningProvider provider; - std::vector<CScript> scripts; - if (!desc->Expand(i, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); - } + UniValue addresses = DeriveAddresses(desc.get(), range_begin, range_end, key_provider); - for (const CScript& script : scripts) { - CTxDestination dest; - if (!ExtractDestination(script, dest)) { - // ExtractDestination no longer returns true for P2PK since it doesn't have a corresponding address - // However combo will output P2PK and should just ignore that script - if (scripts.size() > 1 && std::get_if<PubKeyDestination>(&dest)) { - continue; - } - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); - } - - addresses.push_back(EncodeDestination(dest)); - } + if (descs.size() == 1) { + return addresses; } - // This should not be possible, but an assert seems overkill: - if (addresses.empty()) { - throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); + UniValue ret(UniValue::VARR); + ret.push_back(addresses); + for (size_t i = 1; i < descs.size(); ++i) { + ret.push_back(DeriveAddresses(descs.at(i).get(), range_begin, range_end, key_provider)); } - - return addresses; + return ret; }, }; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 21bc0e52f1..65e6e40b0d 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -338,15 +338,7 @@ static RPCHelpMan getrawtransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved"); } - // Accept either a bool (true) or a num (>=0) to indicate verbosity. - int verbosity{0}; - if (!request.params[1].isNull()) { - if (request.params[1].isBool()) { - verbosity = request.params[1].get_bool(); - } else { - verbosity = request.params[1].getInt<int>(); - } - } + int verbosity{ParseVerbosity(request.params[1], /*default_verbosity=*/0)}; if (!request.params[2].isNull()) { LOCK(cs_main); @@ -405,11 +397,16 @@ static RPCHelpMan getrawtransaction() CBlockUndo blockUndo; CBlock block; - if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return chainman.m_blockman.IsBlockPruned(*blockindex)) || - !(chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex) && chainman.m_blockman.ReadBlockFromDisk(block, *blockindex))) { + if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return !(blockindex->nStatus & BLOCK_HAVE_MASK))) { TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate()); return result; } + if (!chainman.m_blockman.UndoReadFromDisk(blockUndo, *blockindex)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event."); + } + if (!chainman.m_blockman.ReadBlockFromDisk(block, *blockindex)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Block data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event."); + } CTxUndo* undoTX {nullptr}; auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; }); diff --git a/src/rpc/register.h b/src/rpc/register.h index 65fd29ff08..17ed6c142c 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_RPC_REGISTER_H #define BITCOIN_RPC_REGISTER_H -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep /** These are in one header file to avoid creating tons of single-function * headers for everything under src/rpc/ */ diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 083d1be44f..afd98f8875 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -226,10 +226,10 @@ void JSONRPCRequest::parse(const UniValue& valRequest) throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); strMethod = valMethod.get_str(); if (fLogIPs) - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), + LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), this->authUser, this->peerAddr); else - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); + LogDebug(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); // Parse params const UniValue& valParams{request.find_value("params")}; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 19063fa5be..01f2dc0c0e 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <rpc/server.h> @@ -11,6 +11,7 @@ #include <common/system.h> #include <logging.h> #include <node/context.h> +#include <node/kernel_notifications.h> #include <rpc/server_util.h> #include <rpc/util.h> #include <sync.h> @@ -18,8 +19,7 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> - -#include <boost/signals2/signal.hpp> +#include <validation.h> #include <cassert> #include <chrono> @@ -69,22 +69,6 @@ struct RPCCommandExecution } }; -static struct CRPCSignals -{ - boost::signals2::signal<void ()> Started; - boost::signals2::signal<void ()> Stopped; -} g_rpcSignals; - -void RPCServer::OnStarted(std::function<void ()> slot) -{ - g_rpcSignals.Started.connect(slot); -} - -void RPCServer::OnStopped(std::function<void ()> slot) -{ - g_rpcSignals.Stopped.connect(slot); -} - std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& helpreq) const { std::string strRet; @@ -185,7 +169,7 @@ static RPCHelpMan stop() { // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. - CHECK_NONFATAL((*CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown))()); + CHECK_NONFATAL((CHECK_NONFATAL(EnsureAnyNodeContext(jsonRequest.context).shutdown_request))()); if (jsonRequest.params[0].isNum()) { UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()}); } @@ -295,9 +279,8 @@ bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd) void StartRPC() { - LogPrint(BCLog::RPC, "Starting RPC\n"); + LogDebug(BCLog::RPC, "Starting RPC\n"); g_rpc_running = true; - g_rpcSignals.Started(); } void InterruptRPC() @@ -305,7 +288,7 @@ void InterruptRPC() static std::once_flag g_rpc_interrupt_flag; // This function could be called twice if the GUI has been started with -server=1. std::call_once(g_rpc_interrupt_flag, []() { - LogPrint(BCLog::RPC, "Interrupting RPC\n"); + LogDebug(BCLog::RPC, "Interrupting RPC\n"); // Interrupt e.g. running longpolls g_rpc_running = false; }); @@ -316,11 +299,11 @@ void StopRPC() static std::once_flag g_rpc_stop_flag; // This function could be called twice if the GUI has been started with -server=1. assert(!g_rpc_running); - std::call_once(g_rpc_stop_flag, []() { - LogPrint(BCLog::RPC, "Stopping RPC\n"); + std::call_once(g_rpc_stop_flag, [&]() { + LogDebug(BCLog::RPC, "Stopping RPC\n"); WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear()); DeleteAuthCookie(); - g_rpcSignals.Stopped(); + LogDebug(BCLog::RPC, "RPC stopped.\n"); }); } @@ -583,7 +566,7 @@ void RPCRunLater(const std::string& name, std::function<void()> func, int64_t nS throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); LOCK(g_deadline_timers_mutex); deadlineTimers.erase(name); - LogPrint(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); + LogDebug(BCLog::RPC, "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); deadlineTimers.emplace(name, std::unique_ptr<RPCTimerBase>(timerInterface->NewTimer(func, nSeconds*1000))); } diff --git a/src/rpc/server.h b/src/rpc/server.h index 56e8a63088..5a22279a58 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -18,12 +18,6 @@ class CRPCCommand; -namespace RPCServer -{ - void OnStarted(std::function<void ()> slot); - void OnStopped(std::function<void ()> slot); -} - /** Query whether RPC is running */ bool IsRPCRunning(); diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index 7958deb677..40294fda06 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -10,6 +10,7 @@ #include <merkleblock.h> #include <node/blockstorage.h> #include <primitives/transaction.h> +#include <rpc/blockchain.h> #include <rpc/server.h> #include <rpc/server_util.h> #include <rpc/util.h> @@ -96,6 +97,10 @@ static RPCHelpMan gettxoutproof() } } + { + LOCK(cs_main); + CheckBlockDataAvailability(chainman.m_blockman, *pblockindex, /*check_for_undo=*/false); + } CBlock block; if (!chainman.m_blockman.ReadBlockFromDisk(block, *pblockindex)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cc49670198..d71d7d737b 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <clientversion.h> #include <common/args.h> @@ -19,6 +19,7 @@ #include <script/signingprovider.h> #include <script/solver.h> #include <tinyformat.h> +#include <uint256.h> #include <univalue.h> #include <util/check.h> #include <util/result.h> @@ -80,6 +81,18 @@ void RPCTypeCheckObj(const UniValue& o, } } +int ParseVerbosity(const UniValue& arg, int default_verbosity) +{ + if (!arg.isNull()) { + if (arg.isBool()) { + return arg.get_bool(); // true = 1 + } else { + return arg.getInt<int>(); + } + } + return default_verbosity; +} + CAmount AmountFromValue(const UniValue& value, int decimals) { if (!value.isNum() && !value.isStr()) @@ -102,11 +115,11 @@ CFeeRate ParseFeeRate(const UniValue& json) uint256 ParseHashV(const UniValue& v, std::string_view name) { const std::string& strHex(v.get_str()); - if (64 != strHex.length()) - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d, for '%s')", name, 64, strHex.length(), strHex)); - if (!IsHex(strHex)) // Note: IsHex("") is false - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be hexadecimal string (not '%s')", name, strHex)); - return uint256S(strHex); + if (auto rv{uint256::FromHex(strHex)}) return *rv; + if (auto expected_len{uint256::size() * 2}; strHex.length() != expected_len) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d, for '%s')", name, expected_len, strHex.length(), strHex)); + } + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be hexadecimal string (not '%s')", name, strHex)); } uint256 ParseHashO(const UniValue& o, std::string_view strKey) { @@ -1345,24 +1358,26 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl } std::string error; - auto desc = Parse(desc_str, provider, error); - if (!desc) { + auto descs = Parse(desc_str, provider, error); + if (descs.empty()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } - if (!desc->IsRange()) { + if (!descs.at(0)->IsRange()) { range.first = 0; range.second = 0; } std::vector<CScript> ret; for (int i = range.first; i <= range.second; ++i) { - std::vector<CScript> scripts; - if (!desc->Expand(i, provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); - } - if (expand_priv) { - desc->ExpandPrivate(/*pos=*/i, provider, /*out=*/provider); + for (const auto& desc : descs) { + std::vector<CScript> scripts; + if (!desc->Expand(i, provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); + } + if (expand_priv) { + desc->ExpandPrivate(/*pos=*/i, provider, /*out=*/provider); + } + std::move(scripts.begin(), scripts.end(), std::back_inserter(ret)); } - std::move(scripts.begin(), scripts.end(), std::back_inserter(ret)); } return ret; } diff --git a/src/rpc/util.h b/src/rpc/util.h index 23024376e0..b8e6759768 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -101,6 +101,15 @@ std::vector<unsigned char> ParseHexV(const UniValue& v, std::string_view name); std::vector<unsigned char> ParseHexO(const UniValue& o, std::string_view strKey); /** + * Parses verbosity from provided UniValue. + * + * @param[in] arg The verbosity argument as a bool (true) or int (0, 1, 2,...) + * @param[in] default_verbosity The value to return if verbosity argument is null + * @returns An integer describing the verbosity level (e.g. 0, 1, 2, etc.) + */ +int ParseVerbosity(const UniValue& arg, int default_verbosity); + +/** * Validate and return a CAmount from a UniValue number or string. * * @param[in] value UniValue number or string to parse. |