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.
2751ret.pushKV("relevant_blocks", std::move(blocks));2752ret.pushKV("completed", completed);2753} else {2754throw JSONRPCError(RPC_INVALID_PARAMETER, tfm::format("Invalid action '%s'", action));2755}2756return ret;2757},2758};2759}2761static RPCMethod getdescriptoractivity()2762{2763return RPCMethod{2764"getdescriptoractivity",2765"Get spend and receive activity associated with a set of descriptors for a set of blocks. "2766"This command pairs well with the `relevant_blocks` output of `scanblocks()`.\n"2767"This call may take several minutes. If you encounter timeouts, try specifying no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",2768{2769RPCArg{"blockhashes", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of blockhashes to examine for activity. Order doesn't matter. Must be along main chain or an error is thrown.\n", {2770{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A valid blockhash"},2771}},2772RPCArg{"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::NO, "The list of descriptors (scan objects) to examine for activity. Every scan object is either a string descriptor or an object:",2773{2774{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},2775output_descriptor_obj,2776},2777RPCArgOptions{.oneline_description="[scanobjects,...]"},2778},2779{"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to include unconfirmed activity"},2780},2781RPCResult{2782RPCResult::Type::OBJ, "", "", {2783{RPCResult::Type::ARR, "activity", "events", {2784{RPCResult::Type::OBJ, "", "", {2785{RPCResult::Type::STR, "type", "always 'spend'"},2786{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the spent output"},2787{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The blockhash this spend appears in (omitted if unconfirmed)"},2788{RPCResult::Type::NUM, "height", /*optional=*/true, "Height of the spend (omitted if unconfirmed)"},2789{RPCResult::Type::STR_HEX, "spend_txid", "The txid of the spending transaction"},2790{RPCResult::Type::NUM, "spend_vin", "The input index of the spend"},2791{RPCResult::Type::STR_HEX, "prevout_txid", "The txid of the prevout"},2792{RPCResult::Type::NUM, "prevout_vout", "The vout of the prevout"},2793{RPCResult::Type::OBJ, "prevout_spk", "", ScriptPubKeyDoc()},2794}},2795{RPCResult::Type::OBJ, "", "", {2796{RPCResult::Type::STR, "type", "always 'receive'"},2797{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the new output"},2798{RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block that this receive is in (omitted if unconfirmed)"},2799{RPCResult::Type::NUM, "height", /*optional=*/true, "The height of the receive (omitted if unconfirmed)"},2800{RPCResult::Type::STR_HEX, "txid", "The txid of the receiving transaction"},2801{RPCResult::Type::NUM, "vout", "The vout of the receiving output"},2802{RPCResult::Type::OBJ, "output_spk", "", ScriptPubKeyDoc()},2803}},2804// TODO is the skip_type_check avoidable with a heterogeneous ARR?2805}, {.skip_type_check=true}, },2806},2807},2808RPCExamples{2809HelpExampleCli("getdescriptoractivity", "'[\"000000000000000000001347062c12fded7c528943c8ce133987e2e2f5a840ee\"]' '[\"addr(bc1qzl6nsgqzu89a66l50cvwapnkw5shh23zarqkw9)\"]'")2810},2811[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue2812{2813UniValue ret(UniValue::VOBJ);2814UniValue activity(UniValue::VARR);2815NodeContext& node = EnsureAnyNodeContext(request.context);2816ChainstateManager& chainman = EnsureChainman(node);2818struct CompareByHeightAscending {2819bool operator()(const CBlockIndex* a, const CBlockIndex* b) const {2820return a->nHeight < b->nHeight;2821}2822};2824std::set<const CBlockIndex*, CompareByHeightAscending> blockindexes_sorted;2826{2827// Validate all given blockhashes, and ensure blocks are along a single chain.2828LOCK(::cs_main);2829for (const UniValue& blockhash : request.params[0].get_array().getValues()) {2830uint256 bhash = ParseHashV(blockhash, "blockhash");2831CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(bhash);2832if (!pindex) {2833throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");2834}2835if (!chainman.ActiveChain().Contains(*pindex)) {2836throw JSONRPCError(RPC_INVALID_PARAMETER, "Block is not in main chain");2837}2838blockindexes_sorted.insert(pindex);2839}2840}2842std::set<CScript> scripts_to_watch;2844// Determine scripts to watch.2845for (const UniValue& scanobject : request.params[1].get_array().getValues()) {2846FlatSigningProvider provider;2847std::vector<CScript> scripts = EvalDescriptorStringOrObject(scanobject, provider);2849for (const CScript& script : scripts) {2850scripts_to_watch.insert(script);2851}2852}2854const auto AddSpend = [&](2855const CScript& spk,2856const CAmount val,2857const CTransactionRef& tx,2858int vin,2859const CTxIn& txin,2860const CBlockIndex* index2861) {2862UniValue event(UniValue::VOBJ);2863UniValue spkUv(UniValue::VOBJ);2864ScriptToUniv(spk, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);2866event.pushKV("type", "spend");2867event.pushKV("amount", ValueFromAmount(val));2868if (index) {2869event.pushKV("blockhash", index->GetBlockHash().ToString());2870event.pushKV("height", index->nHeight);2871}2872event.pushKV("spend_txid", tx->GetHash().ToString());2873event.pushKV("spend_vin", vin);2874event.pushKV("prevout_txid", txin.prevout.hash.ToString());2875event.pushKV("prevout_vout", txin.prevout.n);2876event.pushKV("prevout_spk", spkUv);2878return event;2879};2881const auto AddReceive = [&](const CTxOut& txout, const CBlockIndex* index, int vout, const CTransactionRef& tx) {2882UniValue event(UniValue::VOBJ);2883UniValue spkUv(UniValue::VOBJ);2884ScriptToUniv(txout.scriptPubKey, /*out=*/spkUv, /*include_hex=*/true, /*include_address=*/true);2886event.pushKV("type", "receive");2887event.pushKV("amount", ValueFromAmount(txout.nValue));2888if (index) {2889event.pushKV("blockhash", index->GetBlockHash().ToString());2890event.pushKV("height", index->nHeight);2891}2892event.pushKV("txid", tx->GetHash().ToString());2893event.pushKV("vout", vout);2894event.pushKV("output_spk", spkUv);2896return event;2897};2899BlockManager* blockman;2900Chainstate& active_chainstate = chainman.ActiveChainstate();2901{2902LOCK(::cs_main);2903blockman = CHECK_NONFATAL(&active_chainstate.m_blockman);2904}2906for (const CBlockIndex* blockindex : blockindexes_sorted) {2907const CBlock block{GetBlockChecked(chainman.m_blockman, *blockindex)};2908const CBlockUndo block_undo{GetUndoChecked(*blockman, *blockindex)};2910for (size_t i = 0; i < block.vtx.size(); ++i) {2911const auto& tx = block.vtx.at(i);2913if (!tx->IsCoinBase()) {2914// skip coinbase; spends can't happen there.2915const auto& txundo = block_undo.vtxundo.at(i - 1);2917for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {2918const auto& coin = txundo.vprevout.at(vin_idx);2919const auto& txin = tx->vin.at(vin_idx);2920if (scripts_to_watch.contains(coin.out.scriptPubKey)) {2921activity.push_back(AddSpend(2922coin.out.scriptPubKey, coin.out.nValue, tx, vin_idx, txin, blockindex));2923}2924}2925}2927for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {2928const auto& vout = tx->vout.at(vout_idx);2929if (scripts_to_watch.contains(vout.scriptPubKey)) {2930activity.push_back(AddReceive(vout, blockindex, vout_idx, tx));2931}2932}2933}2934}2936bool search_mempool = true;2937if (!request.params[2].isNull()) {2938search_mempool = request.params[2].get_bool();2939}2941if (search_mempool) {2942const CTxMemPool& mempool = EnsureMemPool(node);2943LOCK(::cs_main);2944LOCK(mempool.cs);2945const CCoinsViewCache& coins_view = &active_chainstate.CoinsTip();2947for (const CTxMemPoolEntry& e : mempool.entryAll()) {2948const auto& tx = e.GetSharedTx();2950for (size_t vin_idx = 0; vin_idx < tx->vin.size(); ++vin_idx) {2951CScript scriptPubKey;2952CAmount value;2953const auto& txin = tx->vin.at(vin_idx);2954std::optional<Coin> coin = coins_view.GetCoin(txin.prevout);2956// Check if the previous output is in the chain2957if (!coin) {2958// If not found in the chain, check the mempool. Likely, this is a2959// child transaction of another transaction in the mempool.2960CTransactionRef prev_tx = CHECK_NONFATAL(mempool.get(txin.prevout.hash));2962if (txin.prevout.n >= prev_tx->vout.size()) {2963throw std::runtime_error("Invalid output index");2964}2965const CTxOut& out = prev_tx->vout[txin.prevout.n];2966scriptPubKey = out.scriptPubKey;2967value = out.nValue;2968} else {2969// Coin found in the chain2970const CTxOut& out = coin->out;2971scriptPubKey = out.scriptPubKey;2972value = out.nValue;2973}2975if (scripts_to_watch.contains(scriptPubKey)) {2976UniValue event(UniValue::VOBJ);2977activity.push_back(AddSpend(2978scriptPubKey, value, tx, vin_idx, txin, nullptr));2979}2980}2982for (size_t vout_idx = 0; vout_idx < tx->vout.size(); ++vout_idx) {2983const auto& vout = tx->vout.at(vout_idx);2984if (scripts_to_watch.contains(vout.scriptPubKey)) {2985activity.push_back(AddReceive(vout, nullptr, vout_idx, tx));2986}2987}2988}2989}2991ret.pushKV("activity", activity);2992return ret;2993},2994};2995}2997static RPCMethod getblockfilter()2998{2999return RPCMethod{3000"getblockfilter",