Web3 engineering
Ethereum questions for engineers building apps on chain: transaction lifecycle, nonces, fees, finality, signatures, account abstraction, reliable reads, and contract security basics.
Answer out loud or on paper before revealing. If your answer misses a key point, revisit the linked guide or explainer.
Walk through the lifecycle of an Ethereum transaction from signing to finality.
Reveal model answer
The wallet builds a transaction (to, value, data, nonce, gas limit, maxFeePerGas, maxPriorityFeePerGas, chainId) and signs it with the account's private key, which also fixes its hash. It is sent to a node over JSON-RPC (eth_sendRawTransaction), validated, and gossiped into the mempool, where it waits until a block builder or proposer includes it; until then it can be dropped or replaced. Once included, the receipt shows status (success or revert), gas used, and logs, but the block can still be reorganized away. As validators attest, the block becomes part of a justified and then finalized checkpoint, typically after about two epochs (around 13 minutes), after which reverting it would require destroying at least a third of all staked ETH.
WHAT A STRONG ANSWER COVERS
- Sign locally; the node only relays.
- Mempool: pending, replaceable, droppable.
- Included is not final; a reverted transaction still costs gas.
- Justified then finalized checkpoints, about two epochs.
- Check receipt status, not just inclusion.
RED FLAGS
- Treating a transaction hash returned by the RPC as proof of success.
- Assuming a mined transaction cannot disappear.
LIKELY FOLLOW-UPS
- What would you show the user at each stage?
- Why might a transaction sit pending for hours?
SOURCES
What is an account nonce, and how do you manage nonces in a backend that sends many transactions?
Reveal model answer
For an externally owned account, the nonce is the count of transactions it has sent; each new transaction must use exactly the next nonce, which prevents replay and orders an account's transactions. A gap blocks everything after it: if nonce 5 is never mined, 6 and 7 wait in the mempool. A backend hot wallet should therefore assign nonces from a single place (a DB row locked per account, or one sender process), rather than asking eth_getTransactionCount for each send, which races between workers. To unstick or cancel a transaction, resend the same nonce with higher fees (a replacement, typically needing a meaningful bump in both fee fields), for example a zero-value transfer to yourself as a cancel.
WHAT A STRONG ANSWER COVERS
- Strictly sequential per account; gaps block later transactions.
- Prevents replay and orders an account's transactions.
- Centralized nonce allocation for concurrent senders.
- Speed up or cancel by replacing the same nonce with higher fees.
- Contract account nonces count contract creations instead.
RED FLAGS
- Calling eth_getTransactionCount with "latest" from several workers and sending in parallel.
- Thinking a stuck transaction can be cancelled without spending gas.
LIKELY FOLLOW-UPS
- How would you recover after your nonce tracker and the chain disagree?
- How does ERC-4337 handle nonces differently?
SOURCES
How do EIP-1559 transaction fees work?
Reveal model answer
Each block has a protocol-set base fee per gas that rises or falls by at most 12.5 percent per block depending on whether the previous block used more or less than the gas target (half the gas limit), and the base fee is burned. A type-2 transaction sets maxFeePerGas, the most it will pay per gas in total, and maxPriorityFeePerGas, the tip to the block proposer. The effective price per gas is min(maxFeePerGas, baseFee + maxPriorityFeePerGas), the difference up to the max is not charged, and the transaction is not includable while the base fee exceeds its max fee. Total cost is gas used times effective price, and a common wallet heuristic sets the max fee to about twice the current base fee plus the tip so it survives several full blocks.
WHAT A STRONG ANSWER COVERS
- Base fee is algorithmic, adjusts up to 12.5 percent per block, and is burned.
- Priority fee goes to the proposer.
- Effective price = min(maxFee, baseFee + priorityFee).
- Pay for gas used, not the gas limit.
- Headroom in maxFee avoids getting stuck when the base fee rises.
RED FLAGS
- Thinking you always pay maxFeePerGas.
- Confusing the gas limit with the gas price.
- Setting maxFee equal to the current base fee.
LIKELY FOLLOW-UPS
- Why can a transaction with a high tip still be stuck?
- How would you estimate fees for a backend that must confirm within a minute?
SOURCES
What are reorgs and finality, and how should an exchange credit deposits?
Reveal model answer
A reorg happens when the fork-choice rule switches the canonical head to a competing branch, so recent blocks and the transactions in them can disappear or change order. On Ethereum proof of stake, blocks become justified and then finalized through checkpoint votes by two thirds of stake, normally about two epochs (12 seconds per slot, 32 slots per epoch); finalized blocks cannot be reverted without a third of stake being slashed. A deposit service should therefore index the chain with block hashes and parent hashes, detect reorgs and roll back derived state, show deposits as pending at inclusion, and only credit spendable balances once the block is finalized (or after a risk-based depth for small amounts). Layer 2s and other chains have different finality rules, so the policy must be per chain.
WHAT A STRONG ANSWER COVERS
- Reorgs remove or reorder unfinalized blocks.
- Finality via 2/3 stake checkpoints, about two epochs.
- Track block hashes and parent hashes; handle rollbacks in the indexer.
- Pending at inclusion, credited at finality.
- Per-chain policy; L2 soft confirmation differs from L1 finality.
RED FLAGS
- Crediting on the first confirmation for large amounts.
- Indexing by block number only, so a reorg silently mixes branches.
- Applying Bitcoin-style "6 confirmations" reasoning without knowing the chain's finality.
LIKELY FOLLOW-UPS
- What would you do if finality stalls for an hour?
- How do you reconcile an indexer after a deep reorg?
SOURCES
How do you verify a user's signature for sign-in or an off-chain order? Cover EIP-712 and ERC-1271.
Reveal model answer
Have the user sign EIP-712 typed data rather than an opaque string: the wallet shows readable fields, and the digest is keccak256("\x19\x01" || domainSeparator || hashStruct(message)), where the domain binds name, version, chainId, and verifyingContract so a signature cannot be replayed on another chain or contract. EIP-712 itself has no replay protection, so the message must include a nonce and an expiry that you check and consume server-side or on chain. For an EOA you recover the signer with ecrecover and compare addresses (rejecting malleable high-s values, as OpenZeppelin's ECDSA library does); for a smart contract wallet there is no private key, so you call isValidSignature(hash, signature) on the account per ERC-1271 and accept only the magic value 0x1626ba7e. Libraries that verify both paths avoid locking out smart-account and multisig users.
WHAT A STRONG ANSWER COVERS
- EIP-712 domain separation (chainId, verifyingContract).
- Replay protection is the application's job: nonce plus expiry.
- EOA: ecrecover and compare; reject malleable signatures.
- Contract wallets: ERC-1271 isValidSignature returning 0x1626ba7e.
- Verify on the same chain the signature is scoped to.
RED FLAGS
- Signing a plain message like "login" with no nonce, domain, or expiry.
- Only supporting ecrecover, which breaks multisig and smart wallets.
- Trusting a client-supplied address without checking it matches the recovered signer.
LIKELY FOLLOW-UPS
- How does Sign-In with Ethereum structure its message?
- How would you verify a signature from a smart account that is not deployed yet?
SOURCES
What is account abstraction? Compare ERC-4337 and EIP-7702.
Reveal model answer
Account abstraction lets accounts define their own validation logic, enabling batching, gas sponsorship, session keys, passkeys, and social recovery instead of one hard-coded ECDSA key. ERC-4337 does this without a consensus change: users sign UserOperations sent to a separate mempool, bundlers package them into a transaction to the singleton EntryPoint contract, which calls the smart account's validateUserOp and then executes, with optional paymasters paying gas and factories deploying accounts at counterfactual addresses. EIP-7702, live on mainnet since the Pectra upgrade on 7 May 2025, adds a type-4 transaction whose authorization list lets an existing EOA delegate its code to a contract, so a familiar address gains smart-account features. The original private key still has full control, a malicious or buggy delegate can drain the account, and checks like require(msg.sender == tx.origin) no longer guarantee the caller is a plain EOA.
WHAT A STRONG ANSWER COVERS
- Goal: programmable validation and payment of gas.
- ERC-4337: UserOperation, bundler, EntryPoint, paymaster, factory; no protocol change.
- EIP-7702: type-4 set-code transaction, EOA delegates to contract code; active since Pectra.
- They compose: a 7702-delegated EOA can act as a 4337 account.
- Security: delegate trust, the key still being root, broken tx.origin assumptions.
RED FLAGS
- Saying EIP-7702 converts the EOA permanently and removes the private key.
- Signing a 7702 authorization for an unaudited delegate contract, or with chain_id 0 (valid on every chain) without understanding it.
- Using tx.origin checks as a reentrancy or bot guard.
LIKELY FOLLOW-UPS
- How would a paymaster protect itself from being drained?
- What changes for a dapp that assumed every user is an EOA?
SOURCES
How do you read chain state reliably from a backend?
Reveal model answer
Pin every related read to the same block: get a block, then pass its hash (EIP-1898 lets eth_call, eth_getBalance and friends take a blockHash, with requireCanonical) so balances and contract calls reconcile even if a reorg or a different load-balanced node answers the next request. Choose the block tag deliberately: latest for UI freshness, safe or finalized for anything that moves money or credits users. Behind a load balancer, RPC nodes can be several blocks apart, so a follow-up read can go backwards in time; detect that by tracking the highest block seen and retrying on a node that has caught up, or use one provider per session. Index events with getLogs over bounded ranges with retries, store block hashes to detect reorgs, and treat RPC responses as untrusted input that can time out or be wrong.
WHAT A STRONG ANSWER COVERS
- Consistent snapshot: query by block hash, not by "latest" repeatedly.
- latest versus safe versus finalized tags.
- Load-balanced RPC nodes can lag; never let observed height go backwards.
- Bounded getLogs ranges, retries, and reorg-aware indexing.
- Cross-check critical reads with a second provider.
RED FLAGS
- Two separate "latest" calls assumed to see the same state.
- Crediting users from a pending-state read.
- Unbounded eth_getLogs from block 0.
LIKELY FOLLOW-UPS
- How would you build an indexer that survives provider outages?
- When would you run your own node instead of using a provider?
SOURCES
What is reentrancy, and how does the checks-effects-interactions pattern prevent it?
Reveal model answer
Any external call or ETH transfer hands control to another contract, which can call back into the caller before the first invocation finishes. If the caller has not yet updated its state, as in a withdraw that sends ETH before zeroing the balance, the attacker re-enters and withdraws repeatedly; this is how The DAO was drained in 2016. Checks-effects-interactions orders every function as: validate inputs and permissions, update state, and only then make external calls, so a re-entrant call sees the already-updated state. Add a reentrancy guard (a mutex modifier such as OpenZeppelin's ReentrancyGuard) for complex flows, remember that read-only and cross-function reentrancy also exist, and never use tx.origin for authorization.
WHAT A STRONG ANSWER COVERS
- External calls transfer control; callbacks can re-enter.
- Checks, then effects, then interactions.
- Reentrancy guards for cross-function cases.
- Pull-over-push payments reduce exposure.
- Use msg.sender for authorization, not tx.origin.
RED FLAGS
- "Using transfer() with its 2300 gas stipend makes it safe." Gas costs change, and stipend assumptions break.
- Thinking only ETH transfers can re-enter (token hooks such as ERC-777 and ERC-721 safeTransfer callbacks can too).
LIKELY FOLLOW-UPS
- What is read-only reentrancy, and why does it affect protocols that read prices from other contracts?
- Which other contract vulnerabilities would you check in a review?
SOURCES