Bitcoin in Practice: Transaction Validation
By Kurt Wuckert Jr.
Transaction validation, what nodes actually do all day
A node’s day job is simple to describe and hard to do at scale: validate transactions against the current UTXO set so the same coins cannot be spent twice, then propagate valid transactions to the rest of the network. The same validation criteria also applies when the node is validating transactions inside a block.
Transaction validation is not one monolithic step. It is a chain of components that together take a raw transaction, check it for correctness, check that the inputs really exist and are spendable, run the scripts, then either reject it or accept it and move it into the mempool.
The core checks a transaction must pass
Here is the logical checklist described in the source, stated plainly:
The transaction’s syntax and structure must be correct.
The inputs list and outputs list cannot be empty.
The transaction must not be too large, and it must not be absurdly small:
Size is less than the Maximum Block Size value (as referenced by the material).
Size is at least 100 bytes.
Output values must be sane:
Each output value, and the total, must be in the allowed range 0 < x < 21*10^6 (as written in the source).
Inputs must not contain a null hash in script.
nLockTimemust be <= 0xFFFFFFFF.The number of signature operations must be under the signature operation limit.
The unlocking script and locking script must comply with the validation rules.
For every input, the previous output being referenced must be real:
A matching transaction must exist in the pool or in a block in the main branch for all inputs.
If the referenced output is already being spent by another transaction in the mempool, treat it as a double spend attempt and reject.
If the referenced output is a coinbase output, it must have at least 100 block confirmations.
The referenced output must exist and must not already be spent.
Values must balance:
Sum(inputs) must be in range, sum(outputs) must be in range.
Reject if sum(inputs) < sum(outputs).
Reject if the transaction fee would be too low to get into an empty block (as described).
Finally, scripts must prove spend authority:
The unlocking scripts for each input must validate against the corresponding output locking scripts.
When nodes validate, and why it matters
Nodes do not have to be online forever. The whitepaper notes that nodes can leave and rejoin, accepting the proof of work chain as proof of what happened while they were gone.
But when a node does receive a transaction, it must validate it and propagate it. A node can stop accepting transactions, but it risks losing competitiveness against nodes that accept everything they can.
The four main components in transaction validation
When a new transaction arrives through the network layer (BSN), it is passed to a transaction manager that kicks off validation. The flow described breaks into four components.
TxManager
TxManager does the first-pass work:
Consumes the incoming transaction message.
Checks basics like schema, completeness, and non-duplication.
Splits the handling into two tracks, one for inputs and one for outputs.
For inputs, it validates the UTXOs being spent, then forwards the transaction toward full validation.
Calls transaction storage to store the raw transaction once the basic checks pass.
In parallel, it triggers UTXO storage to record the outputs as newly created UTXOs.
TxStorage
TxStorage is straightforward: it stores the raw transaction after TxManager’s sanity checks, like duplication checks and basic validity.
TxValidator
TxValidator is where full validation happens:
Runs the transaction against consensus rules and local policy rules.
Fetches the UTXOs being spent from UTXO storage.
Constructs the script evaluations that need to be performed.
For each input, it pairs the input’s unlocking script with the previous output’s locking script and runs them through the script engine.
If any check fails at any stage, validation stops and a failure response is passed back through the network layer.
If everything passes, the valid transaction is sent to the mempool.
bitcoin ScriptEngine
The ScriptEngine executes the script validations requested by TxValidator and returns a success or failure result.
UTXO storage, the state of who can spend what
The UTXO set is the current state of ownership for all satoshi tokens in existence.
Except for coinbase transactions, every valid transaction spends at least one UTXO and creates zero or more new UTXOs, plus possibly unspendable outputs. A UTXO might be created and spent in milliseconds, or it might sit for decades. Either way, unspent outputs have to be persisted as long as they remain unspent.
A useful analogy from the source is the envelope model:
A UTXO is like an envelope holding tokens.
Once you open an envelope and spend it, you cannot close it and reuse it.
Tokens move into new envelopes as new outputs.
Wallets are basically collections of envelopes.
Keys map to that envelope concept too:
The private key acts like a seal that prevents others from opening the envelope.
The public key or address is where the envelope is “sent” or made accessible.
Nodes verify the seal conditions before an envelope can be opened.
The source also notes that an envelope can be sealed by multiple parties, meaning the rules for opening can require multiple approvals.
It also describes an exceptional case: a court can order the gatekeepers, the nodes, to open an envelope, meaning miners would comply with a legal directive even if it breaks the seal.
On the storage side, UTXO storage is designed to handle both short-term and long-term access patterns, including keeping some UTXOs hot in cache while others live in longer-term storage.
During validation, nodes lock a UTXO entry while checking it so no other transaction can spend it at the same time. After successful script validation and acceptance into the mempool, the locked UTXO status is changed to spent. New UTXOs created by the transaction become available for later spending.
UTXO state is also tied to the current chain tip. If the tip changes, UTXO state changes with it, and that alignment is kept in sync via the chain tracker.
Mempool, where valid transactions wait
The mempool is a temporary cache for validated, unconfirmed transactions. It manages and publishes unconfirmed transactions by:
Keeping transactions in memory for a period of time
Tracking coins spent by mempool transactions
Tracking UTXOs created by mempool transactions
Staying consistent when the blockchain changes, including new blocks and reorgs
Cleaning up stale transactions and limiting total mempool size
It also serves the block assembler by providing topology-sorted mineable transactions, handling removal cascades (a removed parent means children must be removed too), and maintaining pools for low-fee, non-final, and time-locked transactions.
The source describes mempool “types” as terminology over one storage:
Primary mempool: selected for the next block
Secondary mempool: not selected for the next block
Non-final mempool: unconfirmed transactions
Transactions expire out of the mempool, with a default described as two weeks after entering.
When blocks arrive, mempool contents update. Conceptually:
If another node mines a block, remove mined transactions, remove double spends, and remove the children of those double spends.
If the node mines locally, remove the mined transactions.
Block assembler, building a candidate block
The block assembler takes validated transactions from the mempool and builds a fresh block template.
It coordinates with the chain tracker, mempool, and UTXO storage. It fetches the current chain tip, collects verified transactions, orders them in first-seen order, builds the Merkle tree from transaction IDs, and calculates the Merkle root. It then constructs the block header based on current difficulty.
In parallel, mining pool software constantly asks the assembler for updated candidates so miners are always hashing on the freshest block template available.
Block validation, accepting blocks from the network
Nodes must accept and extend valid blocks, which requires validating not only the block metadata but also every transaction inside the block.
When the network layer learns of a new proposed block, it triggers block validation. The node coordinates with the chain tracker to understand the relevant tip, downloads the block, validates the block metadata, then validates every transaction in the block.
The source notes that typically about 97 percent of a proposed block’s transactions are already known to the node, because it has been validating transactions as they arrived and tracking them in the mempool. The remaining unseen transactions are fed into the transaction validation process.
Once transactions are validated, the node recalculates the Merkle tree from the transaction list and checks it against the block header. If everything matches and passes, the node accepts the block, updates the chain tip, and begins building on top of that block.
ELI5(ish...)
Think of bitcoin like a giant public notebook that records who handed what to whom.
A transaction is someone saying, “I am allowed to spend this money, and I am giving it to these new people.”
Nodes are the referees. They check two things:
Are you trying to spend money that does not exist, or money you already spent?
Did you bring the right proof, like the right key, to unlock what you are spending?
The spendable money in the system lives in “envelopes” called UTXOs. If you open an envelope to spend it, that envelope is done forever, and you create new envelopes for the next owners, including any change back to yourself.
When a node sees a transaction, it runs a bunch of checks to make sure it is well-formed, not too big, and that the math makes sense, inputs cover outputs plus fees. Then it runs the scripts, which are basically the rules for opening the envelopes.
If it passes, the transaction goes into the mempool, which is like a waiting room. The block assembler grabs transactions from that waiting room and builds the next candidate page for the notebook. When someone mines a block and publishes it, everyone checks the page, checks the transactions, checks the Merkle root matches, and if it all lines up, they accept it and start building on top of it.