Skip to content

Commit

Permalink
Add start of GethDBReader
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Nov 18, 2023
1 parent 20ef5ae commit 0e43357
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 45 deletions.
49 changes: 49 additions & 0 deletions crates/db-utils/src/leveldb/key.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
//! Helpers for dealing with Geth's rawdb schema and constructing keys.
//!
//! See: https://github.com/ethereum/go-ethereum/blob/c8a22020287e0260e2310a1b91a1aa9b795ca445/core/rawdb/schema.go
use leveldb::database::key::Key;

pub const HEADER_PREFIX: u8 = b"h"[0];
pub const HEADER_HASH_SUFFIX: u8 = b"n"[0];
pub const BLOCK_BODY_PREFIX: u8 = b"b"[0];

/// Wrapper around a [Vec<u8>] to implement the [Key] trait.
pub struct DBKey(Vec<u8>);

impl DBKey {
/// Get a key for the `hash by number` table in the Geth leveldb.
///
/// Format: `header_hash_prefix ++ number ++ header_hash_suffix -> header_hash`
pub fn hash_by_number(number: u64) -> Self {
const KEY_SIZE: usize = 1 + 8 + 1;

let mut key = Vec::with_capacity(KEY_SIZE);
key.push(HEADER_PREFIX);
key.extend_from_slice(&number.to_be_bytes());
key.push(HEADER_HASH_SUFFIX);
Self(key)
}

/// Get a key for the `header by number + hash` table in the Geth leveldb.
///
/// Format: `header_prefix ++ number ++ hash -> header_rlp`
pub fn header_lookup(hash: [u8; 32], number: u64) -> Self {
const KEY_SIZE: usize = 1 + 8 + 32;

let mut key = Vec::with_capacity(KEY_SIZE);
key.push(HEADER_PREFIX);
key.extend_from_slice(&number.to_be_bytes());
key.extend_from_slice(&hash);
Self(key)
}

/// Get a key for the `block by number + hash` table in the Geth leveldb.
///
/// Format: `block body prefix ++ number ++ hash -> block_body_rlp`
pub fn body_by_hash(hash: [u8; 32], number: u64) -> Self {
const KEY_SIZE: usize = 1 + 8 + 32;

let mut key = Vec::with_capacity(KEY_SIZE);
key.push(BLOCK_BODY_PREFIX);
key.extend_from_slice(&number.to_be_bytes());
key.extend_from_slice(&hash);
Self(key)
}
}

impl From<Vec<u8>> for DBKey {
fn from(key: Vec<u8>) -> Self {
Self(key)
Expand Down
149 changes: 104 additions & 45 deletions crates/db-utils/src/leveldb/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,99 @@
use alloy_rlp::Decodable;
use anyhow::{anyhow, Result};
use leveldb::{database::Database, kv::KV, options::ReadOptions};
use reth_primitives::{Header, SealedBlock, SealedHeader, TransactionSigned};

mod key;
pub use self::key::DBKey;

/// A wrapper around a [leveldb] [Database] instance to read data from a Geth database into
/// [reth_primitives] types.
pub struct GethDBReader {
db: Database<DBKey>,
}

impl GethDBReader {
/// Create a new [GethDBReader] instance.
///
/// ### Takes
/// - `database`: An open handle to a Geth [leveldb] database.
pub fn new(database: Database<DBKey>) -> Self {
Self { db: database }
}

/// Retrieve a [SealedHeader] by its block number from a Geth LevelDB
///
/// ### Takes
/// - `db`: A reference to a [Database] instance
/// - `number`: The block number of the [SealedHeader] to retrieve
///
/// ### Returns
/// - Success: A [SealedHeader] instance
/// - Failure: An [anyhow::Error] if the header could not be found
pub fn header_by_number(&self, number: u64) -> Result<SealedHeader> {
// Fetch the header hash
let header_hash_key = DBKey::hash_by_number(number);
let header_hash: [u8; 32] = self
.db
.get(ReadOptions::new(), header_hash_key)?
.ok_or(anyhow::anyhow!("Header hash not found"))?
.try_into()
.map_err(|_| anyhow!("Header hash received from DB is not 32 bytes in size"))?;

// Fetch the header RLP
let header_key = DBKey::header_lookup(header_hash, number);
let header_rlp = self
.db
.get(ReadOptions::new(), header_key)?
.ok_or(anyhow::anyhow!("Header RLP not found"))?;

// Decode the header
let unsealed_header = Header::decode(&mut header_rlp.as_slice())
.map_err(|e| anyhow!("RLP decode error: {e}"))?;

// Return the sealed header
Ok(unsealed_header.seal(header_hash.into()))
}

/// Retrieve a [SealedBlock] by its block number from a Geth LevelDB.
///
/// ### Takes
/// - `db`: A reference to a [Database] instance
/// - `number`: The block number of the [SealedBlock] to Retrieve
///
/// ### Returns
/// - Success: A [SealedBlock] instance
/// - Failure: An [anyhow::Error] if the block could not be found
pub fn block_by_number(&self, number: u64) -> Result<SealedBlock> {
let header = self.header_by_number(number)?;

let body_key = DBKey::body_by_hash(*header.hash, header.number);
let body_rlp = self
.db
.get(ReadOptions::new(), body_key)?
.ok_or(anyhow::anyhow!("Body RLP not found"))?;

// Decode the transactions in the block body
// Why geth? ... Why?
let mut transactions = <Vec<Vec<TransactionSigned>>>::decode(&mut body_rlp.as_slice())?;

Ok(SealedBlock {
header,
body: transactions.remove(0),
ommers: Vec::new(),
withdrawals: None,
})
}
}

#[cfg(test)]
mod db_test {
use super::key::DBKey;
use alloy_primitives::hex;
use alloy_rlp::Decodable;
use leveldb::{
database::Database,
kv::KV,
options::{Options, ReadOptions},
};
use reth_primitives::Header;
use crate::leveldb::GethDBReader;
use leveldb::{database::Database, options::Options};
use std::path::PathBuf;

const HEADER_PREFIX: u8 = b"h"[0];
const NUM_SUFFIX: u8 = b"n"[0];
const TEST_BLOCK_NO: u64 = 4_000_000;

#[test]
#[ignore]
Expand All @@ -24,41 +103,21 @@ mod db_test {

let options = Options::new();
let database: Database<DBKey> = Database::open(db_path.as_path(), options).unwrap();
let reader = GethDBReader::new(database);

dbg!(reader.header_by_number(TEST_BLOCK_NO).unwrap());
}

#[test]
#[ignore]
fn sanity_read_block() {
let mut db_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
db_path.push("testdata/geth/chaindata");

let options = Options::new();
let database: Database<DBKey> = Database::open(db_path.as_path(), options).unwrap();
let reader = GethDBReader::new(database);

let block_number = 4_000_000u64;

// Formulate the block hash key
// Table 1: header prefix ++ number ++ num suffix
let mut block_hash_key = vec![HEADER_PREFIX];
block_hash_key.extend_from_slice(&block_number.to_be_bytes());
block_hash_key.push(NUM_SUFFIX);

// Get blockhash first
let read_opts = ReadOptions::new();
let block_hash = database
.get(read_opts, &block_hash_key.into())
.unwrap()
.unwrap();

println!(
"Found block hash for block #{block_number}: 0x{}",
hex::encode(&block_hash)
);

// Formulate the header key
// Table 2: header prefix ++ number ++ hash
let mut header_key = vec![HEADER_PREFIX];
header_key.extend_from_slice(&block_number.to_be_bytes());
header_key.extend(block_hash);

let read_opts = ReadOptions::new();
let header = database
.get(read_opts, &header_key.into())
.unwrap()
.unwrap();

// RLP Decode the header
let header = Header::decode(&mut header.as_slice()).unwrap();
dbg!(header);
dbg!(reader.block_by_number(TEST_BLOCK_NO).unwrap());
}
}

0 comments on commit 0e43357

Please sign in to comment.