Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
3001"Retrieve a BIP 157 content filter for a particular block.\n",3002{3003{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"},3004{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},3005},3006RPCResult{3007RPCResult::Type::OBJ, "", "",3008{3009{RPCResult::Type::STR_HEX, "filter", "the hex-encoded filter data"},3010{RPCResult::Type::STR_HEX, "header", "the hex-encoded filter header"},3011}},3012RPCExamples{3013HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") +3014HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\", \"basic\"")3015},3016[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue3017{3018uint256 block_hash = ParseHashV(request.params[0], "blockhash");3019auto filtertype_name{self.Arg<std::string_view>("filtertype")};3021BlockFilterType filtertype;3022if (!BlockFilterTypeByName(filtertype_name, filtertype)) {3023throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");3024}3026BlockFilterIndex* index = GetBlockFilterIndex(filtertype);3027if (!index) {3028throw JSONRPCError(RPC_MISC_ERROR, tfm::format("Index is not enabled for filtertype %s", filtertype_name));3029}3031const CBlockIndex* block_index;3032bool block_was_connected;3033{3034ChainstateManager& chainman = EnsureAnyChainman(request.context);3035LOCK(cs_main);3036block_index = chainman.m_blockman.LookupBlockIndex(block_hash);3037if (!block_index) {3038throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");3039}3040block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);3041}3043bool index_ready = index->BlockUntilSyncedToCurrentChain();3045BlockFilter filter;3046uint256 filter_header;3047if (!index->LookupFilter(block_index, filter) ||3048!index->LookupFilterHeader(block_index, filter_header)) {3049int err_code;3050std::string errmsg = "Filter not found.";3052if (!block_was_connected) {3053err_code = RPC_INVALID_ADDRESS_OR_KEY;3054errmsg += " Block was not connected to active chain.";3055} else if (!index_ready) {3056err_code = RPC_MISC_ERROR;3057errmsg += " Block filters are still in the process of being indexed.";3058} else {3059err_code = RPC_INTERNAL_ERROR;3060errmsg += " This error is unexpected and indicates index corruption.";3061}3063throw JSONRPCError(err_code, errmsg);3064}3066UniValue ret(UniValue::VOBJ);3067ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));3068ret.pushKV("header", filter_header.GetHex());3069return ret;3070},3071};3072}3074/**3075* RAII class that registers a prune lock in its constructor to prevent3076* block data from being pruned, and removes it in its destructor.3077*/3078class TemporaryPruneLock3079{3080static constexpr const char* LOCK_NAME{"dumptxoutset-rollback"};3081BlockManager& m_blockman;3082public:3083TemporaryPruneLock(BlockManager& blockman, int height) : m_blockman(blockman)3084{3085LOCK(::cs_main);3086m_blockman.UpdatePruneLock(LOCK_NAME, {height});3087LogDebug(BCLog::PRUNE, "dumptxoutset: registered prune lock at height %d", height);3088}3089~TemporaryPruneLock()3090{3091LOCK(::cs_main);3092m_blockman.DeletePruneLock(LOCK_NAME);3093LogDebug(BCLog::PRUNE, "dumptxoutset: released prune lock");3094}3095};3097/**3098* Serialize the UTXO set to a file for loading elsewhere.3099*3100* @see SnapshotMetadata3101*/3102static RPCMethod dumptxoutset()3103{3104return RPCMethod{3105"dumptxoutset",3106"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"3107"This creates a temporary UTXO database when rolling back, keeping the main chain intact. Should the node experience an unclean shutdown the temporary database may need to be removed from the datadir manually.\n"3108"For deep rollbacks, make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0) as it may take several minutes.",3109{3110{"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},3111{"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."},3112{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",3113{3114{"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,3115"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.",3116RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},3117{"in_memory", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, the temporary UTXO-set database used during rollback is kept entirely in memory. This can significantly speed up the process but requires sufficient free RAM (over 10 GB on mainnet)."},3118},3119},3120},3121RPCResult{3122RPCResult::Type::OBJ, "", "",3123{3124{RPCResult::Type::NUM, "coins_written", "the number of coins written in the snapshot"},3125{RPCResult::Type::STR_HEX, "base_hash", "the hash of the base of the snapshot"},3126{RPCResult::Type::NUM, "base_height", "the height of the base of the snapshot"},3127{RPCResult::Type::STR, "path", "the absolute path that the snapshot was written to"},3128{RPCResult::Type::STR_HEX, "txoutset_hash", "the hash of the UTXO set contents"},3129{RPCResult::Type::NUM, "nchaintx", "the number of transactions in the chain up to and including the base block"},3130}3131},3132RPCExamples{3133HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +3134HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +3135HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)") +3136HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456 in_memory=true)")3137},3138[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue3139{3140NodeContext& node = EnsureAnyNodeContext(request.context);3141const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};3142const CBlockIndex* target_index{nullptr};3143const auto snapshot_type{self.Arg<std::string_view>("type")};3144const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]};3145if (options.exists("rollback")) {3146if (!snapshot_type.empty() && snapshot_type != "rollback") {3147throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type));3148}3149target_index = ParseHashOrHeight(options["rollback"], *node.chainman);3150} else if (snapshot_type == "rollback") {3151auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();3152CHECK_NONFATAL(snapshot_heights.size() > 0);3153auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());3154target_index = ParseHashOrHeight(*max_height, *node.chainman);3155} else if (snapshot_type == "latest") {3156target_index = tip;3157} else {3158throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type));3159}3161const ArgsManager& args{EnsureAnyArgsman(request.context)};3162const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(self.Arg<std::string_view>("path")));3163const auto path_info{fs::status(path)};3164// Write to a temporary path and then move into `path` on completion3165// to avoid confusion due to an interruption. If a named pipe passed, write directly to it.3166const fs::path temppath = fs::is_fifo(path_info) ? path : path + ".incomplete";3168if (fs::exists(path_info) && !fs::is_fifo(path_info)) {3169throw JSONRPCError(3170RPC_INVALID_PARAMETER,3171path.utf8string() + " already exists. If you are sure this is what you want, "3172"move it out of the way first");3173}3175FILE* file{fsbridge::fopen(temppath, "wb")};3176AutoFile afile{file};3177if (afile.IsNull()) {3178throw JSONRPCError(3179RPC_INVALID_PARAMETER,3180"Couldn't open file " + temppath.utf8string() + " for writing.");3181}3183UniValue result;3184Chainstate& chainstate{node.chainman->ActiveChainstate()};3185if (target_index == tip) {3186// Dump the txoutset of the current tip3187result = CreateUTXOSnapshot(node, chainstate, std::move(afile), path, temppath);3188} else {3189// Check pruning constraints before attempting rollback and prevent3190// pruning of the necessary blocks with a temporary prune lock3191std::optional<TemporaryPruneLock> temp_prune_lock;3192if (node.chainman->m_blockman.IsPruneMode()) {3193LOCK(node.chainman->GetMutex());3194const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()};3195const CBlockIndex& first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)};3196if (first_block.nHeight > target_index->nHeight) {3197throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned.");3198}3199temp_prune_lock.emplace(node.chainman->m_blockman, target_index->nHeight);3200}3202const bool in_memory{options.exists("in_memory") ? options["in_memory"].get_bool() : false};3203result = CreateRolledBackUTXOSnapshot(node,3204chainstate,3205target_index,3206std::move(afile),3207path,3208temppath,3209in_memory);3210}3212if (!fs::is_fifo(path_info)) {3213fs::rename(temppath, path);3214}3216return result;3217},3218};3219}3221/**3222* RAII class that creates a temporary database directory in its constructor3223* and removes it in its destructor.3224*/3225class TemporaryUTXODatabase3226{3227fs::path m_path;3228public:3229TemporaryUTXODatabase(const fs::path& path) : m_path(path) {3230fs::create_directories(m_path);3231}3232~TemporaryUTXODatabase() {3233if (!DestroyDB(fs::PathToString(m_path))) {3234LogInfo("Failed to clean up temporary UTXO database at %s, please remove it manually.",3235fs::PathToString(m_path));3236}3237}3238};3240UniValue CreateRolledBackUTXOSnapshot(3241NodeContext& node,3242Chainstate& chainstate,3243const CBlockIndex* target,3244AutoFile&& afile,3245const fs::path& path,3246const fs::path& tmppath,3247const bool in_memory)3248{3249// Create a temporary leveldb to store the UTXO set that is being rolled back3250std::string temp_db_name{strprintf("temp_utxo_%d", target->nHeight)};