aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp548
-rw-r--r--src/rpc/blockchain.h10
-rw-r--r--src/rpc/client.cpp7
-rw-r--r--src/rpc/external_signer.cpp2
-rw-r--r--src/rpc/fees.cpp15
-rw-r--r--src/rpc/mempool.cpp112
-rw-r--r--src/rpc/mining.cpp170
-rw-r--r--src/rpc/node.cpp9
-rw-r--r--src/rpc/output_script.cpp126
-rw-r--r--src/rpc/protocol.h3
-rw-r--r--src/rpc/rawtransaction.cpp49
-rw-r--r--src/rpc/register.h2
-rw-r--r--src/rpc/request.cpp25
-rw-r--r--src/rpc/request.h3
-rw-r--r--src/rpc/server.cpp37
-rw-r--r--src/rpc/server.h10
-rw-r--r--src/rpc/txoutproof.cpp5
-rw-r--r--src/rpc/util.cpp59
-rw-r--r--src/rpc/util.h9
19 files changed, 782 insertions, 419 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index e785678614..360f24ec55 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -8,6 +8,7 @@
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
+#include <chainparamsbase.h>
#include <clientversion.h>
#include <coins.h>
#include <common/args.h>
@@ -21,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>
@@ -55,26 +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::Join;
using util::MakeUnorderedList;
-using util::ToString;
-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.
*/
@@ -185,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
@@ -244,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."},
},
@@ -277,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);
@@ -300,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."},
@@ -323,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);
@@ -347,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."},
@@ -370,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);
@@ -433,6 +451,7 @@ static RPCHelpMan getblockfrompeer()
"getblockfrompeer",
"Attempt to fetch block from a given peer.\n\n"
"We must have the header for this block, e.g. using submitheader.\n"
+ "The block will not have any undo data which can limit the usage of the block data in a context where the undo data is needed.\n"
"Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
"Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
"When a peer does not respond with a block, we will disconnect.\n"
@@ -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)) {
@@ -656,9 +682,9 @@ const RPCResult getblock_vin{
{RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
}},
@@ -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;
@@ -786,6 +805,32 @@ static RPCHelpMan getblock()
};
}
+//! Return height of highest block that has been pruned, or std::nullopt if no blocks have been pruned
+std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& chain) {
+ AssertLockHeld(::cs_main);
+
+ // Search for the last block missing block data or undo data. Don't let the
+ // search consider the genesis block, because the genesis block does not
+ // have undo data, but should not be considered pruned.
+ const CBlockIndex* first_block{chain[1]};
+ const CBlockIndex* chain_tip{chain.Tip()};
+
+ // If there are no blocks after the genesis block, or no blocks at all, nothing is pruned.
+ if (!first_block || !chain_tip) return std::nullopt;
+
+ // If the chain tip is pruned, everything is pruned.
+ if (!((chain_tip->nStatus & BLOCK_HAVE_MASK) == BLOCK_HAVE_MASK)) return chain_tip->nHeight;
+
+ const auto& first_unpruned{*CHECK_NONFATAL(blockman.GetFirstBlock(*chain_tip, /*status_mask=*/BLOCK_HAVE_MASK, first_block))};
+ if (&first_unpruned == first_block) {
+ // All blocks between first_block and chain_tip have data, so nothing is pruned.
+ return std::nullopt;
+ }
+
+ // Block before the first unpruned block is the last pruned block.
+ return CHECK_NONFATAL(first_unpruned.pprev)->nHeight;
+}
+
static RPCHelpMan pruneblockchain()
{
return RPCHelpMan{"pruneblockchain", "",
@@ -833,13 +878,12 @@ 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;
}
PruneBlockFilesManual(active_chainstate, height);
- const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())};
- return block.nStatus & BLOCK_HAVE_DATA ? active_chainstate.m_blockman.GetFirstStoredBlock(block)->nHeight - 1 : block.nHeight;
+ return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
},
};
}
@@ -1058,9 +1102,9 @@ static RPCHelpMan gettxout()
{RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", {
- {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg pubkeyhash"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -1249,7 +1293,7 @@ RPCHelpMan getblockchaininfo()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
+ {RPCResult::Type::STR, "chain", "current network name (" LIST_CHAIN_NAMES ")"},
{RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"},
{RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
@@ -1299,8 +1343,8 @@ RPCHelpMan getblockchaininfo()
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
if (chainman.m_blockman.IsPruneMode()) {
- bool has_tip_data = tip.nStatus & BLOCK_HAVE_DATA;
- obj.pushKV("pruneheight", has_tip_data ? chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight : tip.nHeight + 1);
+ const auto prune_height{GetPruneHeight(chainman.m_blockman, active_chainstate.m_chain)};
+ obj.pushKV("pruneheight", prune_height ? prune_height.value() + 1 : 0);
const bool automatic_pruning{chainman.m_blockman.GetPruneTarget() != BlockManager::PRUNE_TARGET_MANUAL};
obj.pushKV("automatic_pruning", automatic_pruning);
@@ -1552,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",
@@ -1566,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()
@@ -1611,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;
},
@@ -1645,13 +1697,19 @@ static RPCHelpMan getchaintxstats()
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM_TIME, "time", "The timestamp for the final block in the window, expressed in " + UNIX_EPOCH_TIME},
- {RPCResult::Type::NUM, "txcount", "The total number of transactions in the chain up to that point"},
+ {RPCResult::Type::NUM, "txcount", /*optional=*/true,
+ "The total number of transactions in the chain up to that point, if known. "
+ "It may be unknown when using assumeutxo."},
{RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"},
- {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "window_interval", /*optional=*/true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"},
- {RPCResult::Type::NUM, "txrate", /*optional=*/true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"},
+ {RPCResult::Type::NUM, "window_tx_count", /*optional=*/true,
+ "The number of transactions in the window. "
+ "Only returned if \"window_block_count\" is > 0 and if txcount exists for the start and end of the window."},
+ {RPCResult::Type::NUM, "txrate", /*optional=*/true,
+ "The average rate of transactions per second in the window. "
+ "Only returned if \"window_interval\" is > 0 and if window_tx_count exists."},
}},
RPCExamples{
HelpExampleCli("getchaintxstats", "")
@@ -1692,19 +1750,23 @@ static RPCHelpMan getchaintxstats()
const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))};
const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()};
- const int nTxDiff = pindex->nChainTx - past_block.nChainTx;
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", (int64_t)pindex->nTime);
- ret.pushKV("txcount", (int64_t)pindex->nChainTx);
+ if (pindex->m_chain_tx_count) {
+ ret.pushKV("txcount", pindex->m_chain_tx_count);
+ }
ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
- ret.pushKV("window_tx_count", nTxDiff);
ret.pushKV("window_interval", nTimeDiff);
- if (nTimeDiff > 0) {
- ret.pushKV("txrate", ((double)nTxDiff) / nTimeDiff);
+ if (pindex->m_chain_tx_count != 0 && past_block.m_chain_tx_count != 0) {
+ const auto window_tx_count = pindex->m_chain_tx_count - past_block.m_chain_tx_count;
+ ret.pushKV("window_tx_count", window_tx_count);
+ if (nTimeDiff > 0) {
+ ret.pushKV("txrate", double(window_tx_count) / nTimeDiff);
+ }
}
}
@@ -2126,14 +2188,14 @@ static const auto scan_result_status_some = RPCResult{
static RPCHelpMan scantxoutset()
{
- // scriptPubKey corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
+ // raw() descriptor corresponding to mainnet address 12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S
const std::string EXAMPLE_DESCRIPTOR_RAW = "raw(76a91411b366edfc0a8b66feebae5c2e25a7b6a5d1cf3188ac)#fm24fxxy";
return RPCHelpMan{"scantxoutset",
"\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
"Examples of output descriptors are:\n"
- " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
- " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
+ " addr(<address>) Outputs whose output script corresponds to the specified address (does not include P2PK)\n"
+ " raw(<hex script>) Outputs whose output script equals the specified hex-encoded bytes\n"
" combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
@@ -2154,7 +2216,7 @@ static RPCHelpMan scantxoutset()
RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", {
{RPCResult::Type::BOOL, "success", "Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"},
- {RPCResult::Type::NUM, "height", "The current block height (index)"},
+ {RPCResult::Type::NUM, "height", "The block height at which the scan was done"},
{RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
{RPCResult::Type::ARR, "unspents", "",
{
@@ -2162,11 +2224,13 @@ static RPCHelpMan scantxoutset()
{
{RPCResult::Type::STR_HEX, "txid", "The transaction id"},
{RPCResult::Type::NUM, "vout", "The vout value"},
- {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"},
- {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"},
+ {RPCResult::Type::STR_HEX, "scriptPubKey", "The output script"},
+ {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched output script"},
{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"},
{RPCResult::Type::BOOL, "coinbase", "Whether this is a coinbase output"},
{RPCResult::Type::NUM, "height", "Height of the unspent transaction output"},
+ {RPCResult::Type::STR_HEX, "blockhash", "Blockhash of the unspent transaction output"},
+ {RPCResult::Type::NUM, "confirmations", "Number of confirmations of the unspent transaction output when the scan was done"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT},
@@ -2256,17 +2320,20 @@ static RPCHelpMan scantxoutset()
const COutPoint& outpoint = it.first;
const Coin& coin = it.second;
const CTxOut& txo = coin.out;
+ const CBlockIndex& coinb_block{*CHECK_NONFATAL(tip->GetAncestor(coin.nHeight))};
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("vout", outpoint.n);
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
unspent.pushKV("coinbase", coin.IsCoinBase());
- unspent.pushKV("height", (int32_t)coin.nHeight);
+ unspent.pushKV("height", coin.nHeight);
+ unspent.pushKV("blockhash", coinb_block.GetBlockHash().GetHex());
+ unspent.pushKV("confirmations", tip->nHeight - coin.nHeight + 1);
unspents.push_back(std::move(unspent));
}
@@ -2601,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
@@ -2609,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, "", "",
@@ -2625,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
@@ -2650,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());
@@ -2661,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;
@@ -2676,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
@@ -2685,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");
}
@@ -2698,11 +2892,24 @@ 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)));
- SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), tip->nHeight, maybe_stats->coins_count};
+ SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};
afile << metadata;
@@ -2733,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) {
@@ -2760,10 +2967,21 @@ UniValue CreateUTXOSnapshot(
result.pushKV("base_height", tip->nHeight);
result.pushKV("path", path.utf8string());
result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
- result.pushKV("nchaintx", tip->nChainTx);
+ result.pushKV("nchaintx", tip->m_chain_tx_count);
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{
@@ -2798,13 +3016,13 @@ static RPCHelpMan loadtxoutset()
}
},
RPCExamples{
- HelpExampleCli("loadtxoutset", "utxo.dat")
+ HelpExampleCli("-rpcclienttimeout=0 loadtxoutset", "utxo.dat")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
- fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(request.params[0].get_str()))};
+ const fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(self.Arg<std::string>("path")))};
FILE* file{fsbridge::fopen(path, "rb")};
AutoFile afile{file};
@@ -2821,34 +3039,24 @@ static RPCHelpMan loadtxoutset()
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("Unable to parse metadata: %s", e.what()));
}
- uint256 base_blockhash = metadata.m_base_blockhash;
- int base_blockheight = metadata.m_base_blockheight;
- if (!chainman.GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
- auto available_heights = chainman.GetParams().GetAvailableSnapshotHeights();
- std::string heights_formatted = Join(available_heights, ", ", [&](const auto& i) { return ToString(i); });
- throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot, "
- "assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s.",
- base_blockhash.ToString(),
- base_blockheight,
- heights_formatted));
+ auto activation_result{chainman.ActivateSnapshot(afile, metadata, false)};
+ if (!activation_result) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
}
- CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main,
- return chainman.m_blockman.LookupBlockIndex(base_blockhash));
- if (!snapshot_start_block) {
- throw JSONRPCError(
- RPC_INTERNAL_ERROR,
- strprintf("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call this RPC again.",
- base_blockhash.ToString()));
- }
- if (!chainman.ActivateSnapshot(afile, metadata, false)) {
- throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to load UTXO snapshot " + fs::PathToString(path));
- }
+ // 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);
result.pushKV("coins_loaded", metadata.m_coins_count);
- result.pushKV("tip_hash", snapshot_start_block->GetBlockHash().ToString());
- result.pushKV("base_height", snapshot_start_block->nHeight);
+ result.pushKV("tip_hash", snapshot_index.GetBlockHash().ToString());
+ result.pushKV("base_height", snapshot_index.nHeight);
result.pushKV("path", fs::PathToString(path));
return result;
},
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index c2021c3608..89b9921d55 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -21,6 +21,7 @@ class CBlockIndex;
class Chainstate;
class UniValue;
namespace node {
+class BlockManager;
struct NodeContext;
} // namespace node
@@ -34,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);
@@ -47,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(
@@ -57,4 +55,8 @@ UniValue CreateUTXOSnapshot(
const fs::path& path,
const fs::path& tmppath);
+//! 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 7dfe69a6c5..601e4fa7bf 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -146,6 +146,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "fundrawtransaction", 1, "conf_target"},
{ "fundrawtransaction", 1, "replaceable"},
{ "fundrawtransaction", 1, "solving_data"},
+ { "fundrawtransaction", 1, "max_tx_weight"},
{ "fundrawtransaction", 2, "iswitness" },
{ "walletcreatefundedpsbt", 0, "inputs" },
{ "walletcreatefundedpsbt", 1, "outputs" },
@@ -164,6 +165,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletcreatefundedpsbt", 3, "conf_target"},
{ "walletcreatefundedpsbt", 3, "replaceable"},
{ "walletcreatefundedpsbt", 3, "solving_data"},
+ { "walletcreatefundedpsbt", 3, "max_tx_weight"},
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
{ "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" },
@@ -185,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" },
@@ -208,6 +212,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "send", 4, "conf_target"},
{ "send", 4, "replaceable"},
{ "send", 4, "solving_data"},
+ { "send", 4, "max_tx_weight"},
{ "sendall", 0, "recipients" },
{ "sendall", 1, "conf_target" },
{ "sendall", 3, "fee_rate"},
@@ -249,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/fees.cpp b/src/rpc/fees.cpp
index aefe78162b..10caa9ed2e 100644
--- a/src/rpc/fees.cpp
+++ b/src/rpc/fees.cpp
@@ -23,7 +23,7 @@
#include <string>
using common::FeeModeFromString;
-using common::FeeModes;
+using common::FeeModesDetail;
using common::InvalidEstimateModeErrorMessage;
using node::NodeContext;
@@ -36,13 +36,8 @@ static RPCHelpMan estimatesmartfee()
"in BIP 141 (witness data is discounted).\n",
{
{"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
- {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n"
- "Whether to return a more conservative estimate which also satisfies\n"
- "a longer history. A conservative estimate potentially returns a\n"
- "higher feerate and is more likely to be sufficient for the desired\n"
- "target, but is not as responsive to short term drops in the\n"
- "prevailing fee market. Must be one of (case insensitive):\n"
- "\"" + FeeModes("\"\n\"") + "\""},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
+ + FeeModesDetail(std::string("default mode will be used"))},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -71,13 +66,13 @@ static RPCHelpMan estimatesmartfee()
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
- bool conservative = true;
+ bool conservative = false;
if (!request.params[1].isNull()) {
FeeEstimateMode fee_mode;
if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
}
- if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
+ if (fee_mode == FeeEstimateMode::CONSERVATIVE) conservative = true;
}
UniValue result(UniValue::VOBJ);
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index fd11f6cfeb..27a00c5d91 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -5,11 +5,13 @@
#include <rpc/blockchain.h>
-#include <kernel/mempool_persist.h>
+#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,10 +26,11 @@
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/time.h>
+#include <util/vector.h>
#include <utility>
-using kernel::DumpMempool;
+using node::DumpMempool;
using node::DEFAULT_MAX_BURN_AMOUNT;
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
@@ -43,7 +46,7 @@ static RPCHelpMan sendrawtransaction()
"\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
"for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
"nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
- "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
+ "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_UTXO_SET, may throw if the transaction cannot be added to the mempool.\n"
"\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
@@ -759,13 +762,13 @@ static RPCHelpMan importmempool()
const UniValue& use_current_time{request.params[1]["use_current_time"]};
const UniValue& apply_fee_delta{request.params[1]["apply_fee_delta_priority"]};
const UniValue& apply_unbroadcast{request.params[1]["apply_unbroadcast_set"]};
- kernel::ImportMempoolOptions opts{
+ node::ImportMempoolOptions opts{
.use_current_time = use_current_time.isNull() ? true : use_current_time.get_bool(),
.apply_fee_delta_priority = apply_fee_delta.isNull() ? false : apply_fee_delta.get_bool(),
.apply_unbroadcast_set = apply_unbroadcast.isNull() ? false : apply_unbroadcast.get_bool(),
};
- if (!kernel::LoadMempool(mempool, load_path, chainstate, std::move(opts))) {
+ if (!node::LoadMempool(mempool, load_path, chainstate, std::move(opts))) {
throw JSONRPCError(RPC_MISC_ERROR, "Unable to import mempool file, see debug.log for details.");
}
@@ -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 2b93c18965..44605cbc89 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -3,10 +3,11 @@
// 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>
+#include <chainparamsbase.h>
#include <common/system.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
@@ -44,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;
@@ -129,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);
@@ -145,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;
@@ -160,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;
}
@@ -180,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()
@@ -345,13 +346,11 @@ static RPCHelpMan generateblock()
std::vector<CTransactionRef> txs;
const auto raw_txs_or_txids = request.params[1].get_array();
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
- const auto str(raw_txs_or_txids[i].get_str());
+ const auto& str{raw_txs_or_txids[i].get_str()};
- uint256 hash;
CMutableTransaction mtx;
- if (ParseHashStr(str, hash)) {
-
- const auto tx = mempool.get(hash);
+ if (auto hash{uint256::FromHex(str)}) {
+ const auto tx{mempool.get(*hash)};
if (!tx) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
}
@@ -371,13 +370,10 @@ static RPCHelpMan generateblock()
ChainstateManager& chainman = EnsureChainman(node);
{
- LOCK(cs_main);
+ std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock(coinbase_script, {.use_mempool = false})};
+ CHECK_NONFATAL(block_template);
- 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;
+ block = block_template->getBlock();
}
CHECK_NONFATAL(block.vtx.size() == 1);
@@ -387,10 +383,8 @@ static RPCHelpMan generateblock()
RegenerateCommitments(block, chainman);
{
- LOCK(cs_main);
-
BlockValidationState state;
- if (!miner.testBlockValidity(state, block, /*check_merkle_root=*/false)) {
+ if (!miner.testBlockValidity(block, /*check_merkle_root=*/false, state)) {
throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("testBlockValidity failed: %s", state.ToString()));
}
}
@@ -398,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.");
}
@@ -428,7 +422,7 @@ static RPCHelpMan getmininginfo()
{RPCResult::Type::NUM, "difficulty", "The current difficulty"},
{RPCResult::Type::NUM, "networkhashps", "The network hashes per second"},
{RPCResult::Type::NUM, "pooledtx", "The size of the mempool"},
- {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"},
+ {RPCResult::Type::STR, "chain", "current network name (" LIST_CHAIN_NAMES ")"},
(IsDeprecatedRPCEnabled("warnings") ?
RPCResult{RPCResult::Type::STR, "warnings", "any network and blockchain warnings (DEPRECATED)"} :
RPCResult{RPCResult::Type::ARR, "warnings", "any network and blockchain warnings (run with `-deprecatedrpc=warnings` to return the latest warning as a single string)",
@@ -667,9 +661,7 @@ static RPCHelpMan getblocktemplate()
ChainstateManager& chainman = EnsureChainman(node);
Mining& miner = EnsureMining(node);
LOCK(cs_main);
- std::optional<uint256> maybe_tip{miner.getTipHash()};
- CHECK_NONFATAL(maybe_tip);
- uint256 tip{maybe_tip.value()};
+ uint256 tip{CHECK_NONFATAL(miner.getTip()).value().hash};
std::string strMode = "template";
UniValue lpval = NullUniValue;
@@ -713,7 +705,7 @@ static RPCHelpMan getblocktemplate()
return "inconclusive-not-best-prevblk";
}
BlockValidationState state;
- miner.testBlockValidity(state, block);
+ miner.testBlockValidity(block, /*check_merkle_root=*/true, state);
return BIP22ValidationResult(state);
}
@@ -746,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())
@@ -767,26 +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);
- std::optional<uint256> maybe_tip{miner.getTipHash()};
- CHECK_NONFATAL(maybe_tip);
- tip = maybe_tip.value();
+ tip = CHECK_NONFATAL(miner.getTip()).value().hash;
if (!IsRPCRunning())
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
@@ -808,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))
{
@@ -822,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);
@@ -844,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++;
@@ -868,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;
@@ -882,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");
@@ -912,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:
{
@@ -921,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;
@@ -941,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);
@@ -968,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;
@@ -1104,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/node.cpp b/src/rpc/node.cpp
index 65b0a93cdd..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>
@@ -221,7 +221,6 @@ static RPCHelpMan logging()
"The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n"
"In addition, the following are available as category names with special meanings:\n"
" - \"all\", \"1\" : represent all logging categories.\n"
- " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
,
{
{"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to add to debug logging",
@@ -245,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 65a9be2762..49f3a81243 100644
--- a/src/rpc/output_script.cpp
+++ b/src/rpc/output_script.cpp
@@ -38,7 +38,7 @@ static RPCHelpMan validateaddress()
{
{RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"},
- {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"},
+ {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded output script generated by the address"},
{RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"},
{RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"},
{RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"},
@@ -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";
@@ -217,7 +264,7 @@ static RPCHelpMan deriveaddresses()
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
" wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
- " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
+ " raw(<hex script>) Outputs whose output script equals the specified hex-encoded bytes\n"
" tr(<pubkey>,multi_a(<n>,<pubkey>,<pubkey>,...)) P2TR-multisig outputs for the given threshold and pubkeys\n"
"\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n"
@@ -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/protocol.h b/src/rpc/protocol.h
index 83a9010681..0574335c96 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -46,14 +46,13 @@ enum RPCErrorCode
RPC_DESERIALIZATION_ERROR = -22, //!< Error parsing or validating structure in raw format
RPC_VERIFY_ERROR = -25, //!< General error during transaction or block submission
RPC_VERIFY_REJECTED = -26, //!< Transaction or block was rejected by network rules
- RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
+ RPC_VERIFY_ALREADY_IN_UTXO_SET = -27, //!< Transaction already in utxo set
RPC_IN_WARMUP = -28, //!< Client still warming up
RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated
//! Aliases for backward compatibility
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
RPC_TRANSACTION_REJECTED = RPC_VERIFY_REJECTED,
- RPC_TRANSACTION_ALREADY_IN_CHAIN= RPC_VERIFY_ALREADY_IN_CHAIN,
//! P2P client errors
RPC_CLIENT_NOT_CONNECTED = -9, //!< Bitcoin is not connected
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 75b538061d..65e6e40b0d 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -85,9 +85,9 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
static std::vector<RPCResult> ScriptPubKeyDoc() {
return
{
- {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"},
};
@@ -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; });
@@ -506,18 +503,18 @@ static RPCHelpMan decodescript()
RPCResult{
RPCResult::Type::OBJ, "", "",
{
- {RPCResult::Type::STR, "asm", "Script public key"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "p2sh", /*optional=*/true,
"address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"},
{RPCResult::Type::OBJ, "segwit", /*optional=*/true,
- "Result of a witness script public key wrapping this redeem script (not returned for types that should not be wrapped)",
+ "Result of a witness output script wrapping this redeem script (not returned for types that should not be wrapped)",
{
- {RPCResult::Type::STR, "asm", "String representation of the script public key"},
- {RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"},
- {RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
+ {RPCResult::Type::STR, "type", "The type of the output script (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
@@ -557,6 +554,7 @@ static RPCHelpMan decodescript()
case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
+ case TxoutType::ANCHOR:
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
@@ -599,6 +597,7 @@ static RPCHelpMan decodescript()
case TxoutType::WITNESS_V0_KEYHASH:
case TxoutType::WITNESS_V0_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT:
+ case TxoutType::ANCHOR:
// Should not be wrapped
return false;
} // no default case, so the compiler can warn about missing cases
@@ -735,7 +734,7 @@ static RPCHelpMan signrawtransactionwithkey()
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
- {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"},
+ {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "output script"},
{"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"},
@@ -833,9 +832,9 @@ const RPCResult decodepsbt_inputs{
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
- {RPCResult::Type::STR, "asm", "Disassembly of the public key script"},
+ {RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
- {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"},
+ {RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"},
}},
@@ -1790,8 +1789,8 @@ static RPCHelpMan joinpsbts()
std::iota(output_indices.begin(), output_indices.end(), 0);
// Shuffle input and output indices lists
- Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
- Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
+ std::shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
+ std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx = CMutableTransaction();
@@ -1838,8 +1837,8 @@ static RPCHelpMan analyzepsbt()
{
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose signature is missing"},
}},
- {RPCResult::Type::STR_HEX, "redeemscript", /*optional=*/true, "Hash160 of the redeemScript that is missing"},
- {RPCResult::Type::STR_HEX, "witnessscript", /*optional=*/true, "SHA256 of the witnessScript that is missing"},
+ {RPCResult::Type::STR_HEX, "redeemscript", /*optional=*/true, "Hash160 of the redeem script that is missing"},
+ {RPCResult::Type::STR_HEX, "witnessscript", /*optional=*/true, "SHA256 of the witness script that is missing"},
}},
{RPCResult::Type::STR, "next", /*optional=*/true, "Role of the next person that this input needs to go to"},
}},
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 87b9f18b33..afd98f8875 100644
--- a/src/rpc/request.cpp
+++ b/src/rpc/request.cpp
@@ -5,12 +5,11 @@
#include <rpc/request.h>
-#include <util/fs.h>
-
#include <common/args.h>
#include <logging.h>
#include <random.h>
#include <rpc/protocol.h>
+#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/strencodings.h>
@@ -95,7 +94,7 @@ static fs::path GetAuthCookieFile(bool temp=false)
static bool g_generated_cookie = false;
-bool GenerateAuthCookie(std::string *cookie_out)
+bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms)
{
const size_t COOKIE_SIZE = 32;
unsigned char rand_pwd[COOKIE_SIZE];
@@ -109,7 +108,7 @@ bool GenerateAuthCookie(std::string *cookie_out)
fs::path filepath_tmp = GetAuthCookieFile(true);
file.open(filepath_tmp);
if (!file.is_open()) {
- LogPrintf("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
+ LogInfo("Unable to open cookie authentication file %s for writing\n", fs::PathToString(filepath_tmp));
return false;
}
file << cookie;
@@ -117,11 +116,21 @@ bool GenerateAuthCookie(std::string *cookie_out)
fs::path filepath = GetAuthCookieFile(false);
if (!RenameOver(filepath_tmp, filepath)) {
- LogPrintf("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
+ LogInfo("Unable to rename cookie authentication file %s to %s\n", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
return false;
}
+ if (cookie_perms) {
+ std::error_code code;
+ fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
+ if (code) {
+ LogInfo("Unable to set permissions on cookie authentication file %s\n", fs::PathToString(filepath_tmp));
+ return false;
+ }
+ }
+
g_generated_cookie = true;
- LogPrintf("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
+ LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
+ LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions()));
if (cookie_out)
*cookie_out = cookie;
@@ -217,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/request.h b/src/rpc/request.h
index 9968426636..24887e8691 100644
--- a/src/rpc/request.h
+++ b/src/rpc/request.h
@@ -11,6 +11,7 @@
#include <string>
#include <univalue.h>
+#include <util/fs.h>
enum class JSONRPCVersion {
V1_LEGACY,
@@ -23,7 +24,7 @@ UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue
UniValue JSONRPCError(int code, const std::string& message);
/** Generate a new RPC authentication cookie and write it to disk */
-bool GenerateAuthCookie(std::string *cookie_out);
+bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms=std::nullopt);
/** Read the RPC authentication cookie from disk */
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
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 5735aff821..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();
@@ -48,7 +42,7 @@ bool RPCIsInWarmup(std::string *outStatus);
class RPCTimerBase
{
public:
- virtual ~RPCTimerBase() {}
+ virtual ~RPCTimerBase() = default;
};
/**
@@ -57,7 +51,7 @@ public:
class RPCTimerInterface
{
public:
- virtual ~RPCTimerInterface() {}
+ virtual ~RPCTimerInterface() = default;
/** Implementation name */
virtual const char *Name() = 0;
/** Factory function for timers.
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 4df4466c49..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)
{
@@ -332,6 +345,14 @@ public:
return obj;
}
+ UniValue operator()(const PayToAnchor& anchor) const
+ {
+ UniValue obj(UniValue::VOBJ);
+ obj.pushKV("isscript", true);
+ obj.pushKV("iswitness", true);
+ return obj;
+ }
+
UniValue operator()(const WitnessUnknown& id) const
{
UniValue obj(UniValue::VOBJ);
@@ -391,8 +412,8 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
switch (terr) {
case TransactionError::MEMPOOL_REJECTED:
return RPC_TRANSACTION_REJECTED;
- case TransactionError::ALREADY_IN_CHAIN:
- return RPC_TRANSACTION_ALREADY_IN_CHAIN;
+ case TransactionError::ALREADY_IN_UTXO_SET:
+ return RPC_VERIFY_ALREADY_IN_UTXO_SET;
default: break;
}
return RPC_TRANSACTION_ERROR;
@@ -1337,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.