Why Rust

A complete Rust implementation of the XRP Ledger node, built from the ground up.

826,000 lines. 17 crates. Tested on mainnet. Not a fork. Same protocol, independent implementation. Like Reth is to Ethereum, xrpld is to the XRP Ledger.

01 - Origin

How a UBRI presentation turned into six months of Rust

Last year, my project was selected for Ripple's University Blockchain Research Initiative. During one of the programme sessions, David Schwartz gave a presentation on the future of the XRP Ledger. He covered a range of topics: zero knowledge proofs, improved cryptographic primitives, and then, almost in passing, he mentioned that it would be interesting to see a Rust implementation of the node.

The other ideas were compelling in the abstract. The Rust comment landed differently. It stayed.

I had been in the XRP Ledger ecosystem for six years at that point. Built applications, maintained tooling, shipped integrations. But all of that work lived above the protocol layer. I had never looked inside the node itself. Never opened rippled's source and tried to understand how ledgers actually close, how peers negotiate, how state gets persisted. I knew the API surface. I did not know the machine underneath.

David's remark shifted something. The question stopped being "should someone build a Rust implementation" and became "what would I learn if I tried." Not as a commercial venture. Not to compete with the existing team. Purely to understand the system I had been building on for half a decade without ever truly comprehending.

I opened the rippled source the following week. Six months later, the result is xrpld.

02 - Development

700 million tokens and a lot of restarts

Six months ago I decided to rebuild xrpld entirely from scratch, reaching for every AI tool available to me. Claude, Codex, and whatever else I could put to work. By the end of it, roughly 700 million tokens and a lot of rewritten code.

A lot of people will argue that AI is not ready for production scale deployment. I would partially agree with that. They are genuinely good assistive tools with a real tendency to hallucinate, and if left on their own they will always chase whatever compiles fastest. That instinct usually means quietly discarding the subtlety that the original C++ code has been tested for throughout the years. Any time I gave them too much autonomy, what came back was a codebase full of silent divergences from protocol behaviour. So I would scrap it, go back to the C++ myself, work out each condition and invariant properly, and feed that understanding back to the AI so we could rebuild that particular flow correctly.

I also made a deliberate choice not to treat this as a file by file port. Instead I built the architectural design first, with the C++ codebase serving as reference for intent rather than a template to copy. From there I made sure to always cover all the edge cases and flows using test cases. Testing became central to everything, because tests have a way of surfacing the things you would otherwise miss. That discipline comes from six years of writing JavaScript. If you know, you know.

From there I worked through each crate individually, starting with the hardest ones. Transaction application, the application core, consensus, ledger acquisition. The simpler pieces like the relational database layer and the RPC handlers came last. Every crate followed the same pattern: understand what the C++ was actually trying to do, design the Rust equivalent, write the tests, connect to the network, and fix whatever the network exposed.

The final stage was where things got genuinely difficult. All the individual pieces existed and worked on their own, but now they had to come together as a functioning node. Integration tests opened up the first round of problems. Serialisation mismatches, stray bytes appearing where they should not be, NuDB writes failing silently, state tree hashes diverging by a single leaf. None of these were simple to debug. Each one required sitting down with both codebases and understanding precisely why the C++ produced one result and the Rust produced something different.

Once all of that was properly resolved, then came the real test. I started the full node and waited for it to sync. It did not. Not that day, not the next, not for nearly a week. I would trace an issue, fix it, rebuild, and wait again. At one point I realised I had forgotten to add debug logs. Had to go back and add them across all the crates. There still might be some left in the codebase. But eventually I found the final blocking bug, rebuilt once more, and a few hours later watched the node sync to mainnet. Then I realised I could not actually test transactions on mainnet. Had to clear out the build and start a testnet node instead. Three or four hours later, submitted a transaction. It failed again. Back to the drawing board. Though by that point I was genuinely happy just to see it running.

While working through those remaining issues I noticed that certain things in the code could be improved further without changing the overall protocol. Things that would improve acquisition speed, reduce memory usage, and clean up operational behaviour. Those improvements are covered separately in the Improvements section.

Anyhow. Finally I managed to get it all to work. The node properly synced to the network, submitted transactions, and would occasionally return mismatched RPC responses compared to the original server. But slowly as I progressed through each discrepancy, it came to fruition. And now we have a new Rust client for the XRP Ledger.

At the end of the day, how something gets built matters less than whether it actually works. The node syncs mainnet. It serves RPC with response parity. It passes transaction application tests. The tooling is a means. The result is what people will judge, and rightly so.

03 - The Language Dilemma

The Language Dilemma

Before writing anything I had to decide which language to write it in. Obviously as mentioned in the first section Rust was the choice, but there were a few other languages I was heavily considering. This is not a web app where I would pick whatever the team already knows to work on. It is something more substantial, requiring a proper ecosystem where I could easily find issues, nuances, and material to use in the codebase.

Go was where my head went first. Goroutines map naturally to peer connections, the standard library is excellent, compilation is amazing. Go was a good choice. But a few things made me think. During initial sync the node loads the entire state tree into memory. Go's garbage collector introduces latency spikes at that heap size, with measured pauses ranging from 1ms to 50ms. Also during consensus, validators exchange proposals in a tight timing window. A long freeze in that window would mean falling behind, not by a lot but still quite a bit, and depending on conditions, not catching up. Which is why I moved on to other options.

Zig came the closest, especially after seeing the success of the Bun runtime. Manual memory management without undefined behaviour, comptime that eliminates runtime cost, C interop that would have let me port rippled modules incrementally. The language is genuinely beautiful. But the ecosystem is not there yet, or maybe it is just my understanding of the language that is not mature enough to tackle it. There is no production async runtime like Tokio and the package manager is still stabilising. Every library I needed on day one, HTTP, crypto, database drivers, serialisation, would need to be written or wrapped from C. I would not have minded that as a big dedicated project, but as a personal project where I wanted to learn about the XRP Ledger itself, it felt like too much additional work. That being said, I would really love to see a Zig client someday, or maybe work on that myself in coming years.

Rust is what remained after the process of elimination. And once I started using it I understood why people are so emphatic about it. Go style concurrency through Tokio without a garbage collector. Zig level memory control with a very mature ecosystem. The borrow checker enforces ownership at compile time so that entire classes of bugs, data races, use after free, dangling references, simply cannot exist in the final binary. The cost is compile time, which can be reduced using parallel processes, and a learning curve that humbles you for the first month. But after coding the whole system in it, I understand the reasons.

"If I have seen further, it is by standing on the shoulders of giants."
- Isaac Newton

I will not call this a full fledged drop in replacement, or a full parity node that has solved everything. It would not be, unless the community intends it to be. What this is, is a new way of looking at something that has had only one answer for over a decade. A foundation which together we can make more stable, more reliable, and a better client with every contribution.

04 - Client Diversity

Why another implementation matters

For its entire history, the XRP Ledger has run on a single node implementation. rippled has carried that responsibility well, and the network has grown significantly because of it. But looking at how other blockchain ecosystems have matured, there is a pattern worth paying attention to.

Ethereum today runs on Geth, Nethermind, Besu, Erigon, and Reth. When a critical bug surfaces in one client, the network does not go down because the others do not share the same code paths. Different teams approach the same protocol from different angles, catch different edge cases, surface different failure modes. The protocol specification becomes the real source of truth rather than any single repository. Solana went through the same evolution when Jump Crypto built Firedancer. Client diversity is not something networks pursue for the sake of it. It is how they become resilient at scale.

To be clear, xrpld does not change the protocol. Transactions serialise identically. Ledger headers hash identically. No amendment is required and no governance vote is needed. To the network, it is just another peer.

05 - What comes next

The node works. It is not finished.

Today, xrpld syncs the XRP Ledger mainnet as a non validator node. It serves over 50 RPC methods with response format parity against rippled. It passes transaction application tests for payments, trust lines, NFTs, AMMs, and account operations. It connects to real peers and produces correct state.

That said, it is not complete. There can be blind spots or misses that might cause issues. I am confident there should not be many, but this should be treated as a beta version and should not be used for production validators yet.

There are things that have not been explored due to obvious practical limitations. Full history, for example. I cannot realistically download and verify the entire ledger history on my own. That is terabytes of data and would require either validator level infrastructure or someone willing to provide access for testing. Some of the newer RPC methods and transaction types, AMMs, MPTs, loan related transactions, have not been fully tested either. Ideally they should work given the architecture, but if something fails, please report it on GitHub.

I will also be honest about another challenge. As many developers or companies who have built on the chain or eventually moved to other blockchains have discussed, there can be delays in communication or support. I do not say that as a criticism. Who would believe a new client is actually being built in the first place? And there are always other priorities to attend to. I give everyone the benefit of the doubt on that front and will continue maturing the client regardless.

The architecture is documented, the crates are modular, and the test infrastructure is there. What it needs now is more people running it, breaking it, and thinking about it in ways I have not.

The code is open. It is ready for scrutiny. And if you care about the long term health of this network, I think that matters.