Most blockchains ask every validator to do everything: gather transactions, agree on order, execute them and store the result. Flow splits that work across specialized node roles, so execution can run on a small set of powerful machines while security still comes from a large, decentralized set of verifiers. On top of that sits Cadence, a smart contract language where assets are resources: typed values that can be moved but never copied or silently lost.
Four roles instead of one
Flow's pipeline divides a block's life into stages, each handled by a different kind of node:
| Role | Job | Hardware |
|---|---|---|
| Collection | Receive transactions, batch them into collections, and pass signed collection guarantees to consensus. Collection nodes work in clusters. | Moderate |
| Consensus | Order collections into blocks with a HotStuff-style BFT protocol, and later seal blocks once their execution has been verified. | Moderate |
| Execution | Execute every transaction, maintain the full state, and publish execution receipts. | Heavy |
| Verification | Check execution results in small chunks and send approvals to consensus. | Light |
A fifth role, access nodes, serves the public API. They submit transactions to collection nodes and answer queries, but they don't take part in consensus.
The important separation is between consensus and execution. Consensus nodes never run transactions; they only agree on order. That's safe because once the order is fixed, execution is deterministic, so there's only one correct result. The question becomes how to check that execution nodes produced it.
How verification keeps execution honest
Execution nodes split each block's result into chunks. Verification nodes are assigned chunks at random, so no node has to re-run a whole block, and an execution node can't predict who will check which part. Each verifier re-executes its chunks and reports whether the results match. Consensus nodes only seal a block once enough approvals cover every chunk.
Flow also requires verifiers to prove they actually did the work, using a cryptographic construction called a Specialized Proof of Confidential Knowledge (SPoCK). Without it, a lazy verifier could approve everything and hope someone else caught errors. An execution node that publishes a wrong result can be challenged and slashed.
The result is a network where the expensive work is done by a few nodes and the checking is done by many. It isn't sharding: there's one shared state, so contracts can call each other directly in the same transaction.
Cadence resources
In Solidity, a token balance is a number in a contract's mapping. In Cadence, a token vault is a resource that lives in the owner's account. Resource types are marked with @, and the language enforces rules at compile time:
- A resource can only be created with
createinside the contract that defines it. - It can't be copied. Assignment moves it, with the
<-operator. - It can't be lost. Every resource must be stored, moved or explicitly
destroyed before its scope ends.
Here's a minimal token vault in Cadence 1.0 syntax, the current version of the language:
access(all) contract ExampleToken {
access(all) entitlement Withdraw
access(all) resource Vault {
access(all) var balance: UFix64
init(balance: UFix64) {
self.balance = balance
}
access(Withdraw) fun withdraw(amount: UFix64): @Vault {
pre {
amount <= self.balance: "Insufficient balance"
}
self.balance = self.balance - amount
return <- create Vault(balance: amount)
}
access(all) fun deposit(from: @Vault) {
self.balance = self.balance + from.balance
destroy from
}
}
access(all) fun createEmptyVault(): @Vault {
return <- create Vault(balance: 0.0)
}
}
withdraw returns a new @Vault, which the caller must deposit somewhere or the transaction won't compile. There's no transfer(to, amount) that updates two numbers in a mapping; tokens physically move from one vault to another. Fields declared access(all) var are publicly readable but can only be assigned inside the resource itself.
Capabilities and entitlements
Access control in Cadence is based on capabilities: references to stored objects that an account chooses to hand out. Entitlements, introduced with Cadence 1.0, decide what a reference is allowed to do.
In the contract above, withdraw requires the Withdraw entitlement. A plain reference, &ExampleToken.Vault, can read balance and call deposit, but not withdraw. Only a reference typed auth(ExampleToken.Withdraw) &ExampleToken.Vault can take tokens out.
Setting up a vault and publishing a deposit-only capability looks like this:
import "ExampleToken"
transaction {
prepare(signer: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account) {
signer.storage.save(<- ExampleToken.createEmptyVault(), to: /storage/exampleVault)
let cap = signer.capabilities.storage.issue<&ExampleToken.Vault>(/storage/exampleVault)
signer.capabilities.publish(cap, at: /public/exampleVault)
}
}
Transactions also declare exactly which account permissions they need. The prepare block above asks for SaveValue, IssueStorageCapabilityController and PublishCapability on the signer's account, and nothing else, so the transaction can't, for example, borrow a withdraw-capable reference to an existing vault. Wallets can show users this list before they sign.
Account storage
Each Flow account has its own storage, organized by paths:
/storage/...holds the actual objects. Only code authorized by the account can access it directly./public/...holds published capabilities that anyone can borrow.
Because assets live in the owner's account rather than in a central contract, a bug in one contract's bookkeeping can't rewrite everyone's balances the way a bad mapping update can. Storage isn't free: an account's storage capacity is tied to the FLOW it holds, so accounts must keep a small minimum balance, and storing more data requires holding more FLOW.
Accounts are more than a key
A Flow address isn't derived from a public key. Accounts are created on-chain, and each can hold multiple keys with weights. A transaction is valid when the signing keys' weights reach the threshold, which gives you multisig and key rotation natively. Transactions also separate the payer, proposer and authorizers, so an app can pay fees for its users without any extra contract.
Account linking lets one account hold a capability to another account. Apps use it for "hybrid custody": an app creates and manages an account for a user who signed in with an email, and the user can later link it to their own wallet and take control of the assets. Treat the exact APIs as something to check in current documentation, since this area has evolved.
Flow EVM alongside Cadence
The Crescendo upgrade in September 2024 shipped Cadence 1.0 and added Flow EVM, an EVM-equivalent environment that runs inside Flow's execution layer. Solidity contracts deploy unchanged, and Foundry, Hardhat, MetaMask and the usual Ethereum tooling work against Flow EVM's RPC endpoint.
The two environments share the same state and the same blocks. Cadence can control EVM addresses through Cadence-Owned Accounts, so a single Cadence transaction can batch several EVM calls atomically, or combine EVM calls with Cadence logic. That lets teams keep Solidity code while using Cadence for account features the EVM lacks.
Summary
| Concept | What it gives you |
|---|---|
| Collection, consensus, execution, verification | Execution scales on powerful nodes while many light nodes verify it |
| Chunked verification with SPoCKs | Cheap, random checking that verifiers can't fake |
| Cadence resources | Assets that can be moved but never copied or lost |
| Capabilities and entitlements | Fine-grained, typed access control instead of onlyOwner checks |
| Multi-key accounts, payer roles, account linking | Multisig, sponsored fees and custody handover built into the protocol |
| Flow EVM | Solidity contracts on the same chain, callable from Cadence |
Get the weekly commit
New blockchain deep dives every week.