Chain State Pruning and at-block Change
Hey everyone!
I’m Alex Huth, a product lead at Stacks Labs.
We are looking for opportunities to drastically improve the performance of the chain and make running and operating nodes and signers more efficient and affordable so we can be ready for massive throughput.
A topic we are investigating chain state growth and size. Today, node and signer operators are feeling the pain of a growing chain state, which is not only getting larger but is accelerating in its growth. Storage costs and hardware requirements are continuing to increase and getting more expensive, and fewer people are able to afford to run nodes. A proposal that we have landed on, like many other blockchains, is allowing users to operate pruned nodes; however, that’s not currently possible today in Stacks.
The big offender preventing this implementation is the at-block function. It is a Clarity function that lets contracts evaluate read-only expressions against the historical state and currently can look back through the entire historical chain state. You pass the block hash and an expression, and it executes as if you were at that block. It has existed since Clarity 1. As a byproduct, nodes must retain the full historical state; every block’s MARF state has to be kept because any contract could reach back to any arbitrary point. This is fundamentally incompatible with pruning.
Known Use Cases and Alternative Patterns
Here are some of the known historical reasons for wanting at-block and some proposed mitigations or alternative patterns:
Voting / Governance Snapshots
The governance contract wanted to know what a user’s token balance was at the block the proposal was created, thus used at-block to check the balance.
Alternative: Record and maintain an explicit snapshot map. Voters prove their balance by referencing the correct checkpoint. This is essentially how OpenZeppelin’s ERC20Votes works; they don’t use historical state reads either, they use explicit checkpoints.
Staking / Reward Accrual
The contract would use at-block to check whether a user was staked at some past block for reward calculations.
Alternative: A Synthetix-style staking rewards model. You never look backwards - you always accumulate forward. Every state-changing action updates the accumulator, and the math works out identically to checking historical state at every block.
Oracle / Price History
The contract needs to look back at recent price data.
Alternative: Store a fixed-size rolling window (ring buffer). You get bounded lookback with constant storage.
Contract Forks and Migration
There are many reasons during a migration or a port where you want to read state from an old contract version.
Alternative: Instead of using at-block to read the old contract’s state at the fork block, do an explicit one-time migration. You read the current state of the old contract - that’s fine because it’s the latest state, not historical, so no at-block is needed. You just have to do the migration before the old contract’s state changes in ways that would invalidate the read.
The insight across all of these: every real use case can be decomposed into one of two categories:
- “I need a snapshot of a value at a point in time” - solved by explicit checkpoints.
- “I need to compute something over a range of historical values” - solved by accumulators or ring buffers.
While at-block is more elegant and requires less contract-side bookkeeping, it pushes the cost onto every single node operator forever. These alternatives push the cost onto the contracts that actually need the data, which is arguably where it should live.
What We Found
We did a substantial amount of analysis on existing contracts that have been called in the past year that have executed the at-block function: there were only eight that looked back for more than 6 tenures in the past year*, and the vast majority were parts of migrations - not particularly necessary to maintain in the future. A handful of contracts that use it or intend to use it were looking back only a short period of time (1–2 tenures).
The Proposal
We propose limiting at-block to look back six tenures.
Calls targeting blocks older than six tenures would return a structured error; how this is handled in particular is still up for discussion. This would be part of a new Clarity version, so part of a hard fork. As a byproduct, state older than the window can then be safely pruned by nodes. We would still leave the option to run archive nodes for full history so we can run the API against it.
While we think this has massive benefits, there will be some ramifications. Any deployed contracts using at-block with deep lookback will be affected. We want to make sure that this is not a trap - that tokens can’t be stranded in smart contracts if an at-block-using contract is written incorrectly - and catch any other edge cases we haven’t thought of.
What We Want to Hear From You
- Are you using or intending to use the
at-blockfunctionality? - Does the six-tenure window work for your use cases? If not, why?
- Are there use cases we’re missing that need a deeper lookback?
- Are there any preferences you want to share?
Thank you for your time reading this, your support, and your feedback. We really appreciate it. 2026 is an exciting year. We’ve got lots of fun stuff to show you.
For more insight into what we’re thinking about, check out Stacks Labs CEO Alex Miller’s 2026 Lookahead.
* EDIT: This originally said there were only eight contracts that used at-block; this was incorrect. Hundreds of deployed contracts have at-block. This was our measure of how many had executed the at-block code path looking back more than 6 tenures in the past year.