Blockchain fundamentals for backend engineers
Article.
What does a blockchain actually guarantee to a backend that reads from it or pays through it? Less than the marketing, and differently from a database. It guarantees an append-only ledger whose history is expensive to rewrite, ordered by a consensus protocol among parties who do not trust each other.
It does not guarantee that a block you just read will still be there in a minute, that a transaction you sent will be included, or that a token behaves like money.
Backend interviews in this area (exchanges, payments with stablecoins, custody, analytics) ask the integrator’s questions: how finality works and how long it takes, how double spending is prevented, what a transaction costs, where the signing key lives, and how to credit a deposit exactly once when the chain can reorganise under you. This note answers those; it is not a cryptography course. Facts are as of October 2026.
Hashes, blocks and Merkle proofs
A block holds a header and a list of transactions. The header includes the hash of the previous header (so changing any old block changes every hash after it) and the Merkle root of the block’s transactions: pairs of hashes are hashed together, level by level, down to one root.
The point of the Merkle tree is the proof: to show one transaction is in a
block, you need only the sibling hash at each level, log2(n) hashes. A
Bitcoin-style tree (double SHA-256) over a million transaction ids:
def h(b: bytes) -> bytes: # Bitcoin: double SHA-256
return hashlib.sha256(hashlib.sha256(b).digest()).digest()
def verify(leaf, path, root): # path: (sibling, sibling_is_left)
acc = leaf
for sib, left in path:
acc = h(sib + acc) if left else h(acc + sib)
return acc == roottransactions: 1,000,000
merkle root: 3ec62376d888fcc2...
proof length: 20 hashes (640 bytes)
verify tx 424242: True
verify a forged tx: False640 bytes prove membership among a million transactions, which is what lets light clients and bridges check inclusion without downloading blocks. Ethereum uses a different structure (Merkle-Patricia tries) for its state, transactions and receipts, with the same proof idea.
Consensus and finality: when is it final?
A database commit is final when it returns. On a public chain, finality is a property you wait for, and the wait differs by chain:
| Chain | How blocks are agreed | When to treat as final |
|---|---|---|
| Bitcoin | proof of work, ~10 min blocks | probabilistic; ~6 blocks (~1 h) by convention |
| Ethereum | proof of stake since 2022-09-15 | the finalized block, ~2 epochs (~13 min) |
| L2 rollup | a sequencer orders, posts to L1 | sequencer receipt is soft; L1 finality is real |
- Bitcoin’s finality is probabilistic: a block can be replaced by a longer competing chain, and each further block makes that exponentially less likely. Exchanges pick a depth per risk (more for larger amounts).
- Ethereum has 12-second slots and 32-slot epochs. A block becomes
finalizedafter about two epochs (12.8 minutes); reverting it would require destroying a third of all staked ETH. The JSON-RPC block tagslatest,safeandfinalizedlet you read at each level of certainty. - Rollups (Arbitrum, Optimism, Base) give fast receipts from a single sequencer; withdrawals back to L1 from an optimistic rollup wait out a challenge window of about 7 days.
This is Byzantine fault tolerance among strangers. Inside your own systems, crash-fault consensus (Raft) is the comparison point: Consensus and leader election.
Double spending: UTXO vs accounts
- UTXO (Bitcoin): coins are unspent transaction outputs. A transaction consumes specific outputs and creates new ones. A second transaction spending the same output is invalid; the chain accepts at most one.
- Accounts (Ethereum): balances live in accounts, and each account’s transactions carry a nonce that must increase by exactly one. Two transactions with the same nonce: at most one is included. A gap blocks everything after it: a stuck transaction at nonce 41 holds 42, 43 and 44 in the pool until 41 is mined or replaced.
Replacing a stuck transaction means sending another with the same nonce and a higher fee (geth’s pool requires at least a 10% bump). Sending “the same payment again” with a new nonce is how a backend pays twice.
Transactions and fees
On Ethereum since EIP-1559, a transaction offers maxFeePerGas and
maxPriorityFeePerGas. Each block has a base fee that is burned and
moves by at most 12.5% per block with demand; the tip goes to the validator.
The cost is gasUsed x effectiveGasPrice.
- A reverted transaction still pays for the gas it used.
- Inclusion is not guaranteed: a transaction whose fee is too low sits in the mempool or is dropped.
- Amounts are integers in base units. ETH has 18 decimals (wei); USDC
and USDT have 6. A float is never acceptable:
0.1 + 0.2in a ledger is a reconciliation incident. Store base units asNUMERICor big integers; see the double-entry ledger.
Smart contracts, from the integrator’s side
Deployed bytecode is immutable, but most token contracts sit behind an upgradeable proxy (EIP-1967), so “immutable” really means “whoever holds the upgrade key can change it”.
Integrations read events (logs): an
ERC-20 transfer emits Transfer(from, to, value), whose topic is
keccak256("Transfer(address,address,uint256)") =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
Since the Pectra upgrade (May 2025, EIP-7702), an ordinary account can delegate to contract code, so “an externally owned account has no code” is no longer an assumption to build on.
Crediting deposits exactly once
The integration most backends build: watch for incoming token transfers and credit users. A simulation (no network) of three polls, with a 2-block reorganisation between the first and second that removes alice’s deposit:
naive, credit on sight: {'alice': '50.00 USDC', 'bob': '60.00 USDC'}
dedup + 2 confirmations: {'alice': '50.00 USDC', 'bob': '20.00 USDC'}
dedup + 3 confirmations: {'bob': '20.00 USDC'}
same, every poll repeated: {'bob': '20.00 USDC'}Three separate bugs and their fixes:
- No idempotency: the naive indexer credited bob’s one deposit three
times, once per poll. The key is
(chain_id, tx_hash, log_index), inserted withON CONFLICT DO NOTHINGin the same transaction as the ledger entry. - Crediting a deposit that a reorg removed: alice’s 50 USDC exists only
on the abandoned fork. Track
(block_number, block_hash)for every block processed; when a new block’sparent_hashdoes not match, roll back to the fork point and reprocess. - Confirmations shallower than the reorg: with 2 confirmations the
2-block reorg still produced a phantom credit; 3 did not. Credit only at a
depth, or at the
finalizedtag (about 13 minutes on Ethereum, no reorg handling needed). Show deposits as “pending” until then.
The table behind it
CREATE TABLE chain_deposits (
chain_id int NOT NULL,
tx_hash bytea NOT NULL,
log_index int NOT NULL,
block_number bigint NOT NULL,
block_hash bytea NOT NULL,
to_address bytea NOT NULL,
amount_base numeric NOT NULL, -- base units: USDC x 10^6
status text NOT NULL, -- seen | confirmed | reorged
PRIMARY KEY (chain_id, tx_hash, log_index)
);A reorg deeper than your confirmation depth must alert a human, never claw back automatically from a user who may already have withdrawn.
Keys and custody
Whoever holds the private key owns the funds; there is no password reset.
- Tiers: a small hot wallet for automated withdrawals, warm and cold storage for the rest, with withdrawal limits and approval quorums between them.
- Sign inside an HSM or KMS, never with a key in an environment variable
or a secrets manager that hands the raw key to your process. AWS KMS
(
ECC_SECG_P256K1) and Google Cloud KMS (EC_SIGN_SECP256K1_SHA256) sign with secp256k1, the curve Bitcoin and Ethereum use, without releasing the key. - MPC (threshold signatures) vs on-chain multisig (Safe): MPC splits the key so no single machine holds it and looks like one signer on chain; multisig enforces the quorum in a contract, visibly, at higher gas cost.
- One nonce manager per hot address: two workers assigning nonces for the same address produce gaps and replacements.
Stablecoin payments and regulation
- The issuer can freeze: USDC and USDT contracts include blacklist functions; a frozen address cannot move funds. Payments in stablecoins inherit that counterparty.
- Sanctions screening applies to addresses: OFAC lists wallet addresses, so screen counterparties before crediting and before sending.
- The Travel Rule requires sending originator and beneficiary data with transfers between providers: in the US above $3,000; in the EU, under the Transfer of Funds Regulation, for every transfer since 2024-12-30.
- Regulation: MiCA’s stablecoin rules have applied in the EU since 2024-06-30. In the US, the GENIUS Act (signed 2025-07-18) creates a federal framework for payment stablecoins, effective by 2027-01-18 at the latest.
Where it breaks in production
- Logs that appear and then vanish: an RPC endpoint load-balanced
across nodes at different heights. Query by
blockHash, or read atsafeorfinalized. - Provider limits: hosted RPCs cap the block range and result count of
eth_getLogs; an indexer that asks for too much gets errors and must paginate. - Missed events on subscriptions:
eth_subscribe("logs")drops silently on reconnect, and on a reorg re-sends logs withremoved: true. Subscriptions are a latency hint; polling with a cursor is the record. - Backfills that fail: many nodes no longer serve pre-Merge history since 2025’s partial history expiry; backfill from an archive provider.
- Tokens that break “amount equals balance change”: fee-on-transfer and
rebasing tokens, and a USDC freeze. Reconcile against
balanceOfat a finalized block. - A stuck hot wallet: one underpriced transaction blocks every later nonce; withdrawals queue until it is replaced.
What to watch: head lag behind a reference node, a reorg-depth histogram, the age of unconfirmed deposits, and drift between your ledger and on-chain balances at a finalized block.
The trade not taken
A hosted indexer or webhook service (The Graph, provider notifications) instead of running your own polling indexer. It removes reorg handling, node operations and pagination from your code, and puts a third party between you and the ledger you are crediting money from. Teams commonly use both: webhooks for low latency, their own cursor-based reconciliation as the record.
Related
Interview angle 3
- “How many confirmations before you credit a USDC deposit on
Ethereum?” - either wait for the
finalizedtag (about 13 minutes, no reorg handling), or credit at N blocks with reorg detection, where N is deeper than any reorg you expect.- Follow-up: your provider returned a Transfer log that later vanished. What happened? A reorg, or a load-balanced node that was ahead. Store block hashes, detect the parent mismatch, roll back unconfirmed rows.
- Follow-up: and if a reorg goes deeper than your confirmations? Alert and investigate; never claw back automatically.
- “How do UTXO and account models each stop a double spend?” - UTXO: an
output can be consumed once. Accounts: a strictly increasing nonce per
account.
- Follow-up: your hot wallet is stuck at nonce 41. What happens to 42-45, and how do you fix it without paying twice? They wait. Re-send nonce 41 with a higher fee (at least 10% more), never the same payment with a new nonce.
- “Where does the hot-wallet key live?” - in an HSM or KMS that signs
without exporting it, behind limits and approvals.
- Follow-up: why not Secrets Manager? It hands the raw key to your process, so anything that can read process memory or logs can take the funds. MPC or multisig adds protection against a single compromised signer.