bedda.tech logobedda.tech
← Back to blog

Rust in the Hot Path: Why KRAIN's L2 Skips Go

Matthew J. Whitney
9 min read
blockchainsmart contractsweb3infrastructure

The first time a Go garbage collector paused our solver node mid-auction, I didn't immediately know what happened. The latency spike showed up in our traces as a clean vertical line, the kind that looks like a network hiccup until you correlate it with GC logs and realize the runtime just decided to stop the world for a few milliseconds at exactly the wrong moment. In a typical web service, that's a rounding error. In an L2 sequencer where the solver is racing to settle cross-chain intents before the next block window closes, it's a missed settlement.

We weren't even past internal testnet at that point. The load was modest. But the failure mode was clear enough that I didn't want to spend the next six months tuning GOGC knobs and hoping the pauses stayed short enough to matter. We had a decision to make about the core infrastructure of Axon Protocol, KRAIN's L2 layer, and that one trace made the decision easier.

We rewrote the solver node in Rust. Then we moved the on-chain logic to Arbitrum Stylus, which lets you deploy Rust smart contracts that compile to WASM and run alongside the EVM. And we committed to Rust end-to-end across the hot path. That choice has consequences worth being honest about.

Why the Blockchain Hot Path Is Different From Normal Infrastructure

Most infrastructure tolerates non-deterministic latency. A few hundred milliseconds of jitter in an API response is annoying but survivable. In L2 sequencer architecture, the hot path has a different character entirely.

The solver node in Axon Protocol processes cross-chain intents and routes settlements. It competes in a timing-sensitive environment where being late means losing the settlement window. The on-chain contracts that receive those settlements need to execute predictably, because gas estimation and block inclusion depend on consistent execution behavior. If your contract runtime pauses or your off-chain solver stutters, you don't just get slow performance. You get incorrect behavior at the protocol level: missed auctions, failed settlements, disputes.

This is why the memory model matters so much in web3 infrastructure specifically. Go's garbage collector has gotten dramatically better over the years, and for most services it's genuinely fine. But "better" still means "pauses that are unpredictable in timing." Rust's ownership model eliminates that category of problem entirely. There is no GC. Memory is freed when the owning scope exits, deterministically, at compile time. The runtime behavior you see in testing is the runtime behavior you get in production.

This isn't a theoretical preference. It's the difference between a system that behaves predictably under the timing constraints of an L2 settlement window and one that occasionally doesn't.

Rust Smart Contracts via Stylus: What You Actually Get

Arbitrum Stylus is the concrete mechanism that makes Rust smart contracts viable on an EVM-compatible chain without forking the chain itself. Stylus contracts compile to WASM, execute in a WASM VM that runs alongside the EVM, and can interop with Solidity contracts through cross-VM calls. The gas model is different from the EVM, generally cheaper for compute-heavy operations, and the execution environment gives you the full Rust type system.

For Axon Protocol, this meant we could write the settlement logic in the same language as the solver node. The data structures that describe an intent in the off-chain solver are the same structures (modulo serialization format) that the on-chain contract works with. That alignment reduces an entire class of bugs: the ones where your off-chain representation and your on-chain representation drift apart because they're maintained in two different languages by two different mental models.

The Stylus SDK handles the ABI encoding boundary, so you're not writing raw WASM. You define your contract's storage layout, your callable functions, and your error types in Rust, and the SDK generates the ABI-compatible interface. The developer experience is meaningfully better than writing Solidity for anyone who already knows Rust. Whether it's better than Solidity for someone coming from a Solidity background is a different question, and I'll get to that.

The Rust language team's ongoing investment in the ecosystem also matters here. The announcement of Rust's first Maintainers in Residence program signals that the language's governance and maintenance infrastructure is maturing. For a team betting core infrastructure on a language, that kind of institutional continuity is relevant. You want the language to still be actively maintained and improving when you're debugging production issues in two years.

The Real Cost: Developer Experience

I want to be direct about this because I've seen too many Rust advocacy posts that treat the learning curve as a minor inconvenience. It is not a minor inconvenience.

The borrow checker is the right tool for the job we're doing. It catches real bugs at compile time that would surface as production incidents in Go or TypeScript. But it also means that a developer who is productive in Go or Solidity will spend weeks, possibly months, feeling like they're fighting the compiler before they start feeling like they're working with it. That transition is real, and it has a cost measured in engineering time.

For Axon Protocol specifically, we made a deliberate choice to staff the team with engineers who either already knew Rust or were willing to invest in learning it as a primary language. That's a recruiting constraint. The pool of engineers who can write idiomatic Rust is smaller than the pool who can write Go, and the pool who can write Rust smart contracts specifically is smaller still.

There's also the tooling maturity gap. The Rust ecosystem for blockchain development is younger than the Solidity ecosystem. Stylus is newer than the EVM. When you hit an edge case, the documentation is thinner, the Stack Overflow answers are fewer, and the error messages from the SDK are sometimes cryptic in ways that Solidity's tooling isn't anymore. You spend more time reading source code and fewer times finding a worked example.

The honest framing is this: you're paying an upfront developer experience tax in exchange for a runtime guarantee that matters specifically in the domain you're building in. If you're building a simple token contract or a standard NFT collection, the Rust path probably costs more than it's worth. If you're building protocol infrastructure where deterministic execution and memory safety are load-bearing properties, the math flips.

What "No GC" Means in Consensus

The solver node is the piece I want to spend a moment on, because it's where the Go-to-Rust rewrite had the most immediate observable effect.

When you remove garbage collection from the hot path, you remove a source of non-determinism that's otherwise very hard to reason about. The Rust solver processes intent batches, runs the settlement logic, and submits transactions to the sequencer. Under load, every allocation in that path is predictable. The memory layout is known at compile time. There are no background threads cleaning up heap objects while the solver is trying to hit a timing window.

The ownership model also forces you to be explicit about shared state. In the Go version, we had several places where goroutines accessed shared data structures with mutex locks that were correct but not obviously correct. The Rust compiler refuses to compile data races, so the concurrent paths through the solver had to be structured in a way the compiler could verify. That process surfaced a few subtle ordering issues that the Go version would have handled without complaint, and probably without incident most of the time, but "most of the time" is not the bar you want for settlement logic.

The tradeoff is that async Rust, specifically tokio-based async code, has its own complexity budget. The async/await model in Rust is more explicit than Go's goroutines, and the error handling patterns are more verbose. Engineers coming from Go often find the goroutine model more intuitive initially. That's a legitimate point in Go's favor for teams without Rust experience.

Where This Leaves the Web3 Infrastructure Stack

The broader pattern I'm seeing across serious L2 and solver infrastructure projects is a gradual shift toward Rust in the components where performance and safety guarantees matter most, with higher-level languages handling the parts of the stack where developer velocity matters more.

Solidity is still the right choice for most on-chain logic on EVM chains, because the tooling is mature, the auditing ecosystem understands it, and the developer pool is large. Rust smart contracts via Stylus make sense when you have compute-heavy logic, when you're already running Rust off-chain and want to share types, or when you're building infrastructure where the EVM's execution model is genuinely limiting.

Go and TypeScript remain reasonable choices for the parts of the stack that aren't in the hot path: indexers, APIs, front-end tooling, monitoring infrastructure. The GC is fine when you're not racing against a block window.

The memory safety conversation is getting louder across the industry. The Seed7 language talk making rounds in programming communities right now is a small signal of a broader anxiety about memory management in systems software. Rust's answer to that anxiety is the most production-proven one available. The Rust Maintainers in Residence program is evidence that the community is investing in sustaining that answer long-term.

For KRAIN and Axon Protocol, the choice was specific to our constraints: a timing-sensitive solver, on-chain settlement logic that needed to share a type system with that solver, and a team willing to pay the learning curve tax upfront. Those constraints don't apply universally.

But if you're architecting L2 infrastructure from scratch today and your settlement logic has real timing requirements, I'd start with Rust and work backward from there. The GC pause we saw in that early testnet trace was a small thing. The decision it forced turned out to be the right one.

Have Questions or Need Help?

Our team is ready to assist you with your project needs.

Contact Us