A real-time Ethereum indexer that writes on-chain data and off-chain mev-boost relay data to a postgres database. Reth's Execution Extensions (ExEx) framework is used for efficient real-time block notifications and processing.
Dataset | Source | Rust Struct | Postgres Table |
---|---|---|---|
Headers | On-chain | HeadersEvent | headers |
Transactions | On-chain | TransactionsEvent | transactions |
Logs | On-chain | LogsEvent | logs |
Traces | On-chain | TracesEvent | traces |
Uncles | On-chain | OmmersEvent | ommers |
Withdrawals | On-chain | WithdrawalsEvent | withdrawals |
Builder Bids | Off-chain (mevboost) | BuilderBidsEvent | builder_bids |
Proposer Payloads | Off-chain (mevboost) | ProposerPayloadsEvent | proposer_payloads |
ERC-20 Transfers | Derived (logs) | Erc20TransfersEvent | erc20_transfers |
Native Transfers | Derived (traces) | NativeTransfersEvent | native_transfers |
Contracts | Derived (traces) | ContractsEvent | contracts |
- Ensure you have Rust and Cargo installed
- Clone this repository
- Set the
DATABASE_URL
environment variable to your Postgres connection string- example:
postgresql://username:password@host:port/database
- example:
- Build and run the project with
cargo run
main.rs
: Initializes the ExEx and processes chain state notificationsindexer.rs
: Component that storesProcessingEvents
and runs them for each blockdb.rs
: Postgres table-create DDL statements that get run once, at node startupevents.rs
: This is where variousProcessingEvents
are stored as well as the wrapperEvent
enumrelay.rs
: Handler for fetching mev-boost relay dataconfig.rs
: Reads in the project configsutils.rs
: Various utility functions used throughout the codebase
The trait ProcessingEvent
is setup to run each time a new block is committed. ProcessingEvent.process()
receives,
or retrieves, data and writes it to Postgres each time a new block is committed. ProcessingEvent.revert()
deletes
data from postgres each time a new block is reverted. Multiple processing events are already setup (HeaderEvent
,
BuilderBidsEvent
, etc.) that correspond one-to-one to postgres tables. However, processing events can also be setup
where one event writes to multiple postgres tables or multiple events write to the same postgres table. At the end
of the day, each ProcessingEvent
setup is compute you are defining for when a block is either commited or reverted
to the chain. In a way this is just an orchestration system where the trigger is a new Ethereum block (12 seconds) and
the tasks/jobs that are being triggered are the compute defined in each ProcessingEvent
implementation.
When a new exex commit notification is received, its chain state is added to a tokio mpsc queue. A tokio task reads
from this mpsc queue and concurrently runs each ProcessingEvent
with the provided chain state. The next block's
processing will not begin until all current block's processing events have completed. This allows a build-up of
blocks in the queue to be possible -- this would happen when a block takes longer than 12 seconds to finish its
processing events. Most processing events are very fast to read/retrieve the data as well as writing it to postgres;
however, getting data from mev-boost relays requires making external http calls to each running relay and these calls
can take a longer time to run. Having the mpsc queue, and allowing a build-up/queue of processing events, gives this
indexer flexibility in the types of processing it can support.
- Create a new struct that implements
ProcessingEvent
- Add this new struct to the
Event
enum - Add this new struct to
config.yaml
and set it totrue
- Make sure any postgres tables the new processing event interacts with has its create ddl defined in
db.rs
- This newly defined processing event/struct will now run each block
Headers
- Canonical block headersTransactions
- Canonical block transactionsLogs
- Smart contract log events emitted from canonical blocksTraces
- Transaction traces for canonical blocks (does not include pending transaction traces)Uncles
- Uncle block headersWithdrawals
- Validator withdrawals from the beacon chain that were processed in canonical blocksBuilder Bids
- Block builder header submissions to mev-boost relays; contains block bid values and timestamps (in ms) relay received the bidProposer Payloads
- Block header mev-boost relays sent to block proposerERC-20 Transfers
- ERC-20 transfers between addresses, based on emitted contract log eventsNative Transfers
- Native ETH transfers as determined by transaction traces (regular transfers and internal calls with value)Contracts
- Deployed contracts (and their code) based on transaction trace "create" actions