Prelude
This post assumes you have a basic understanding of Rust’s syntax and features such as pattern matching, macros, generics, etc. Rust by example is a good place to start if you’re new.
The hottest tool in town is reth by Paradigm. I’m usually someone who prefers to wait until tools are a bit more mainstream before adopting them as you get the benefit of the wisdom of the crowd. If you have an issue, there is a high degree of probability that someone else has solved that.
However, the fact that it only consumed 2TB for a full archival node (as opposed to geth’s 14TB+) was too tempting for me to not dive into it. And so I did, and I highly recommend anyone to check it out, even if you don’t know Rust.
Pros:
- The codebase is clean, well documented and organized
- The project owner are super responsive (tg group)
- Lots of examples to work off from
Cons:
- Rust learning curve takes a few days
Personal Motivation
The thing that caught my eye was this slide from the Rust x Ethereum day livestream:
I cannot stress how much of a nicety it is to be able to extend on reth while receiving upstream changes.
Traditionally, forks of geth (such as flashbot’s builder) would fork out a couple day’s worth of engineering time just to merge in upstream changes from Geth. With reth that is completely eliminated.
Extending Reth
Lets add a custom API eth_getGasUsedByBlock
that accepts a blockTag (“latest”, “pending”, or block number) and returns the gas used for that particular block.
The goal is to be able to query it via curl like so:
curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getGasUsedByBlock","params":["0x42069"],"id":67}' localhost:8545
CLI
We will first begin by adding an additional CLI arg to reth node
- a boolean enabled by passin in an additional flag --extended-eth-namespace
to enable our custom API:
// main.rs
struct RethNodeCliExtended;
impl RethCliExt for RethNodeCliExtended {
type Node = RethExtended;
}
/// Our custom cli args extension that adds one flag to reth default CLI.
#[derive(Debug, Clone, Default, clap::Args)]
struct RethExtended {
/// Enables builder mode
#[clap(long, short)]
pub extend_eth_namespace: bool,
}
Note that the long, short
in clap
stands for enabling the toggling of the arg in a long format (--extended-eth-namespace
) or a short format (-e
). I know I got confused as I thought it meant that it was a long/short type.
API
Next up, we will be adding in the API endpoint eth_getGasUsedByBlock
and the logic needed.
// gasused.rs
#[rpc[server, namespace="eth"]]
pub trait CustomEthNamespace {
#[method(name = "getGasUsedByBlock")]
fn get_gas_used_by_block(&self, block_number: BlockNumberOrTag) -> RpcResult<u64>;
}
pub struct CustomEthNamespaceExt<P> {
provider: P,
}
impl<P> CustomEthNamespaceExt<P>
where
P: BlockReaderIdExt + ReceiptProvider + Clone + Unpin + 'static,
{
pub fn new(provider: P) -> CustomEthNamespaceExt<P> {
Self { provider }
}
}
impl<P> CustomEthNamespaceServer for CustomEthNamespaceExt<P>
where
P: BlockReaderIdExt + ReceiptProvider + Clone + Unpin + 'static,
{
fn get_gas_used_by_block(&self, bn: BlockNumberOrTag) -> RpcResult<u64> {
match self.provider.block_by_number_or_tag(bn) {
Ok(Some(b)) => Ok(b.gas_used),
_ => {
error!("unable to retrieve block {bn:?}");
Err(ErrorObjectOwned::owned(
-1,
"Invalid blockTag provided",
None::<()>,
))
}
}
}
}
Entrypoint
Finally, we define the entrypoint, which is as simple as:
// main.rs
fn main() {
// Parse args
Cli::<RethNodeCliExtended>::parse().run().unwrap();
}
Fin
And thats it! It is that easy to extend on reth. No more engineering days lost on merging conflicts, just pull and goooooo.
Big kudos to the paradigm team, it was such a pleasure to go through the codespace.