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.
The RPC layer parses the submitted hex and enforces caller-facing limits. It does not decide whether the transaction can enter the chain.
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.
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.
CTxMemPool
A node-local set of transactions that passed this node's acceptance rules. Nodes can have different mempools.
ChainstateManager
Owns the active chainstate, block manager and validation path that chooses and connects the best valid chain.
CWallet and script managers
Optional client code that derives destinations, selects coins, creates change, signs and records wallet transactions.
BlockAssembler
Selects mempool transactions under weight, fee and dependency constraints to build a template for mining.
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.
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);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.
72 } else {
78 const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true);
79 if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
80 return HandleATMPError(result.m_state, err_string);
81 } else if (check_max_fee && result.m_base_fees.value() > max_tx_fee) {
82 return TransactionError::MAX_FEE_EXCEEDED;
91 const MempoolAcceptResult result =
92 node.chainman->ProcessTransaction(tx, /*test_accept=*/false);
102 node.mempool->AddUnbroadcastTx(txid);The normal path can test acceptance before performing it, then adds the transaction to the node's unbroadcast set. “Broadcast” is therefore more than sending a packet. Local acceptance happens first for the standard RPC path.
4473 if (msg_type == NetMsgType::TX) {
4483 if (m_chainman.IsInitialBlockDownload()) return;
4485 CTransactionRef ptx;
4486 vRecv >> TX_WITH_WITNESS(ptx);
4507 const auto& [should_validate, package_to_validate] = m_txdownloadman.ReceivedTx(pfrom.GetId(), ptx);
4535 const MempoolAcceptResult result = m_chainman.ProcessTransaction(ptx);
4536 const TxValidationState& state = result.m_state;
4538 if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
4539 ProcessValidTx(pfrom.GetId(), ptx, result.m_replaced_transactions);The peer manager deserializes the transaction, coordinates download state, then calls the same chainstate transaction path. It also handles duplicates, missing parents, packages, permissions and misbehavior around this smaller excerpt.
122 std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
123 {
126 resetBlock();
135 LOCK(::cs_main);
136 CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip();
150 if (m_mempool) {
151 LOCK(m_mempool->cs);
152 m_mempool->StartBlockBuilding();
153 addChunks();
154 m_mempool->StopBlockBuilding();
155 }
218 pblock->hashPrevBlock = pindexPrev->GetBlockHash();The assembler starts from the active tip, chooses transaction chunks from the mempool, builds the coinbase and header, then tests the candidate. Mining hardware searches header values later. The full node code constructs and checks the work template.
4446 bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block)
4453 BlockValidationState state;
4457 LOCK(cs_main);
4464 bool ret = CheckBlock(*block, state, GetConsensus());
4465 if (ret) {
4467 ret = AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block, min_pow_checked);
4481 if (!ActiveChainstate().ActivateBestChain(state, block)) {
4482 LogError("%s: ActivateBestChain failed (%s)\n", __func__, state.ToString());
4483 return false;
4484 }The block receives context-free checks, is accepted into block storage and index state, then competes for activation. Connecting it performs UTXO-dependent validation. A transaction becomes confirmed from this node's view only when its block is connected to the active chain.
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.
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.
SeeConnectBlockWill 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.
SeeAcceptToMemoryPoolA 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.
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*.datRaw 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.datAn optional persisted snapshot of unconfirmed transactions. The mempool remains node-local and is reconstructed against current policy.
wallets/*/wallet.datOptional 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
- Choose one observable behavior.
Start from an RPC, P2P message or test. Search for the handler name and follow concrete calls.
- Keep the state owner in view.
Ask whether the code changes wallet state, mempool state, block index state or the UTXO cache.
- Read tests beside implementation.
Unit and functional tests reveal expected behavior at boundaries and make edge cases easier to name.
- 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.