Bitcoin

Bitcoin Core v31.1 · commit 9be056a8

Follow one transaction through Bitcoin Core.

Bitcoin Core is easier to understand when you stop treating it as one giant program. A transaction crosses a set of boundaries. Each boundary owns a different decision.

This guide follows those decisions in the actual C++ source. You will see where a raw transaction enters, where local policy can refuse it, how a peer handles it, how a miner builds a candidate block, and where confirmed outputs become part of chain state.

One transaction, six code boundaries

The diagram advances automatically when it enters the viewport. Each step names the component, its job, and the source file that performs it. Pause it whenever you want to inspect the call path.

Execution trace1. RPC receives signed bytes
Playing automatically
Transaction path through Bitcoin CoreSix connected components show a transaction moving from RPC decoding to the UTXO set. RPCTXPOOLP2PBLOCKUTXO tx src/rpc/mempool.cppDecode hex, apply caller limits, then call BroadcastTransaction.

The RPC layer parses the submitted hex and enforces caller-facing limits. It does not decide whether the transaction can enter the chain.

1 / 6

The exact source we are reading

This article is pinned to Bitcoin Core v31.1, specifically commit 9be056a8a72b624dae9623b2f7bded92c2a21c91. A pinned commit matters because function names and line numbers move. Every source link below targets that immutable revision.

Bitcoin Core is one implementation of the Bitcoin protocol. The network is the outcome of independently operated nodes enforcing compatible consensus rules. The repository includes a full node, optional wallet, mining interfaces, command line tools, a graphical interface, tests and support libraries. The code is distributed under the MIT license. The short excerpts here retain that attribution and link back to the exact source.

Start with the objects, not the directories

NodeContext is a useful map. It owns or references the connection manager, mempool, peer manager, chainstate manager, indexes, mining interface and wallet clients. It is wiring, rather than business logic. The types it connects tell you where to look.

Network

CConnman and PeerManager

Connections move bytes. Peer processing interprets messages, tracks what a peer knows, requests missing data and hands transactions or blocks to validation.

Unconfirmed state

CTxMemPool

A node-local set of transactions that passed this node's acceptance rules. Nodes can have different mempools.

Confirmed state

ChainstateManager

Owns the active chainstate, block manager and validation path that chooses and connects the best valid chain.

User keys

CWallet and script managers

Optional client code that derives destinations, selects coins, creates change, signs and records wallet transactions.

Candidate blocks

BlockAssembler

Selects mempool transactions under weight, fee and dependency constraints to build a template for mining.

External control

RPC handlers

Parse requests, enforce interface-specific limits and call node or wallet services. RPC is an entry point, not the consensus engine.

A wallet prepares a spend. A node judges it.

The wallet and the node are separate responsibilities. The wallet knows keys and which outputs it can spend. It chooses inputs, creates recipient and change outputs, estimates a fee, and produces signatures. The validation code does not trust the wallet's opinion.

In src/wallet/spend.cpp, transaction construction totals recipients, prepares a fresh change destination, computes the size and future cost of spending change, and derives the effective fee rate. Descriptor script managers connect wallet descriptors to concrete receiving and change scripts. Wallet storage records keys, descriptors and transactions. None of that is required to run a validating node.

Read the call path in five source windows

Select a tab to inspect a small, verified excerpt. The annotation beside each excerpt explains what changes at that boundary.

src/rpc/mempool.cpp · lines 94 to 132Open verified source
94 CMutableTransaction mtx;
95 if (!DecodeHexTx(mtx, request.params[0].get_str())) {
96     throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
97 }
105 CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
127 const TransactionError err = BroadcastTransaction(node,
128                                                   tx,
129                                                   err_string,
130                                                   max_raw_tx_fee,
131                                                   method,
132                                                   /*wait_callback=*/true);
Boundary: bytes to transaction object

The handler decodes caller-supplied hex, applies burn and fee safeguards, then passes an immutable transaction reference into node code. A successful decode says the bytes have transaction structure. It does not mean the spend is valid.

Consensus validity and mempool policy answer different questions

Consensus rules determine whether a block can be accepted by nodes that enforce those rules. Mempool policy determines whether this node will keep and relay an unconfirmed transaction. Policy can be stricter without changing what blocks are valid.

Consensus

Could this transaction be valid in this block?

Checks include available inputs, script execution under the active rule set, value ranges, maturity and block context. A consensus failure invalidates the block.

See ConnectBlock
Policy

Will this node store and relay it before confirmation?

Checks include fee floors, standardness, replacement behavior and mempool resource limits. Another node can configure some policy differently.

See AcceptToMemoryPool

A transaction rejected from one mempool is not automatically consensus-invalid. It might appear in a valid block later. The reverse distinction matters too: being in a mempool gives no promise of confirmation. It can be replaced, evicted, conflict with a confirmed spend, or remain unattractive to block producers.

Confirmation is a state transition over the UTXO set

Bitcoin Core does not calculate account balances as its base state model. It tracks unspent transaction outputs. Each input points to an earlier output by transaction identifier and output index. Connecting a valid transaction spends referenced coins and adds its new outputs as coins.

The mechanics are visible in src/coins.cpp. AddCoins inserts every output under its new outpoint. SpendCoin finds an existing coin, optionally returns its data for undo information, then removes or clears it from the cache. ConnectBlock applies this across the block only after validating the transactions against the current view.

BeforeA:0 · 80,000 satsunspent
transaction spends A:0
AfterB:0 · 50,000 satsB:1 · 29,400 sats600 sats paid as fee

If the active chain changes, undo data allows connected effects to be reversed before another branch is connected. That is why “written to disk” and “currently active” are separate states in the block path.

What lives in memory, on disk and behind RPC

blocks/blk*.dat

Raw block data. Block index metadata is stored separately so the node can locate blocks and reason about competing branches.

chainstate/

The compact LevelDB representation of the current UTXO set and associated metadata. It is the state needed to validate new spends efficiently.

mempool.dat

An optional persisted snapshot of unconfirmed transactions. The mempool remains node-local and is reconstructed against current policy.

wallets/*/wallet.dat

Optional wallet databases, normally SQLite for current wallets. They contain wallet data, not the authoritative chainstate.

The official data directory documentation maps these files. RPC methods sit above the components. A call such as gettxout queries node state, while sendtoaddress needs a loaded wallet. sendrawtransaction can submit an already signed transaction without giving the node wallet keys.

How to continue without getting lost

  1. Choose one observable behavior.

    Start from an RPC, P2P message or test. Search for the handler name and follow concrete calls.

  2. Keep the state owner in view.

    Ask whether the code changes wallet state, mempool state, block index state or the UTXO cache.

  3. Read tests beside implementation.

    Unit and functional tests reveal expected behavior at boundaries and make edge cases easier to name.

  4. Stay on one revision.

    Use a tag or commit for every file while tracing. Comparing line numbers across branches creates false call paths.

For the conceptual model before the C++, read Bitcoin from zero. For the design that preceded this implementation, read the Bitcoin whitepaper explained. To compare its assumptions with privacy-focused design, continue to Bitcoin and Monero whitepapers compared.