Skip to content

Commit

Permalink
feat: add support for async database (#1809)
Browse files Browse the repository at this point in the history
* feat: add support for async database

* fix: update link in docs
  • Loading branch information
morph-dev authored Sep 26, 2024
1 parent f8d8ae9 commit 411d0ef
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 143 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/database/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ std = ["serde?/std"]
serde = ["dep:serde"]
alloydb = [
"std",
"database-interface/asyncdb",
"dep:tokio",
"dep:alloy-provider",
"dep:alloy-eips",
Expand Down
6 changes: 6 additions & 0 deletions crates/database/interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ serde = { version = "1.0", default-features = false, features = [
"rc",
], optional = true }

# asyncdb
tokio = { version = "1.40", optional = true }


[dev-dependencies]
anyhow = "1.0.83"
Expand All @@ -45,3 +48,6 @@ alloy-sol-types = "0.8"
default = ["std"]
std = ["serde?/std"]
serde = ["dep:serde"]
asyncdb = [
"dep:tokio",
]
188 changes: 188 additions & 0 deletions crates/database/interface/src/async_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use core::future::Future;

use primitives::{Address, B256, U256};
use state::{AccountInfo, Bytecode};
use tokio::runtime::{Handle, Runtime};

use crate::{Database, DatabaseRef};

/// The async EVM database interface.
///
/// Contains the same methods as [Database], but it returns [Future] type instead.
///
/// Use [WrapDatabaseAsync] to provide [Database] implementation for a type that only implements this trait.
pub trait DatabaseAsync {
/// The database error type.
type Error: Send;

/// Get basic account information.
fn basic_async(
&mut self,
address: Address,
) -> impl Future<Output = Result<Option<AccountInfo>, Self::Error>> + Send;

/// Get account code by its hash.
fn code_by_hash_async(
&mut self,
code_hash: B256,
) -> impl Future<Output = Result<Bytecode, Self::Error>> + Send;

/// Get storage value of address at index.
fn storage_async(
&mut self,
address: Address,
index: U256,
) -> impl Future<Output = Result<U256, Self::Error>> + Send;

/// Get block hash by block number.
fn block_hash_async(
&mut self,
number: u64,
) -> impl Future<Output = Result<B256, Self::Error>> + Send;
}

/// The async EVM database interface.
///
/// Contains the same methods as [DatabaseRef], but it returns [Future] type instead.
///
/// Use [WrapDatabaseAsync] to provide [DatabaseRef] implementation for a type that only implements this trait.
pub trait DatabaseAsyncRef {
/// The database error type.
type Error: Send;

/// Get basic account information.
fn basic_async_ref(
&self,
address: Address,
) -> impl Future<Output = Result<Option<AccountInfo>, Self::Error>> + Send;

/// Get account code by its hash.
fn code_by_hash_async_ref(
&self,
code_hash: B256,
) -> impl Future<Output = Result<Bytecode, Self::Error>> + Send;

/// Get storage value of address at index.
fn storage_async_ref(
&self,
address: Address,
index: U256,
) -> impl Future<Output = Result<U256, Self::Error>> + Send;

/// Get block hash by block number.
fn block_hash_async_ref(
&self,
number: u64,
) -> impl Future<Output = Result<B256, Self::Error>> + Send;
}

/// Wraps a [DatabaseAsync] or [DatabaseAsyncRef] to provide a [`Database`] implementation.
#[derive(Debug)]
pub struct WrapDatabaseAsync<T> {
db: T,
rt: HandleOrRuntime,
}

impl<T> WrapDatabaseAsync<T> {
/// Wrap a [DatabaseAsync] or [DatabaseAsyncRef] instance.
///
/// Returns `None` if no tokio runtime is available or if the current runtime is a current-thread runtime.
pub fn new(db: T) -> Option<Self> {
let rt = match Handle::try_current() {
Ok(handle) => match handle.runtime_flavor() {
tokio::runtime::RuntimeFlavor::CurrentThread => return None,
_ => HandleOrRuntime::Handle(handle),
},
Err(_) => return None,
};
Some(Self { db, rt })
}

/// Wrap a [DatabaseAsync] or [DatabaseAsyncRef] instance, with a runtime.
///
/// Refer to [tokio::runtime::Builder] on how to create a runtime if you are in synchronous world.
/// If you are already using something like [tokio::main], call [WrapDatabaseAsync::new] instead.
pub fn with_runtime(db: T, runtime: Runtime) -> Self {
let rt = HandleOrRuntime::Runtime(runtime);
Self { db, rt }
}

/// Wrap a [DatabaseAsync] or [DatabaseAsyncRef] instance, with a runtime handle.
///
/// This generally allows you to pass any valid runtime handle, refer to [tokio::runtime::Handle] on how
/// to obtain a handle. If you are already in asynchronous world, like [tokio::main], use [WrapDatabaseAsync::new]
/// instead.
pub fn with_handle(db: T, handle: Handle) -> Self {
let rt = HandleOrRuntime::Handle(handle);
Self { db, rt }
}
}

impl<T: DatabaseAsync> Database for WrapDatabaseAsync<T> {
type Error = T::Error;

#[inline]
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
self.rt.block_on(self.db.basic_async(address))
}

#[inline]
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.rt.block_on(self.db.code_by_hash_async(code_hash))
}

#[inline]
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
self.rt.block_on(self.db.storage_async(address, index))
}

#[inline]
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
self.rt.block_on(self.db.block_hash_async(number))
}
}

impl<T: DatabaseAsyncRef> DatabaseRef for WrapDatabaseAsync<T> {
type Error = T::Error;

#[inline]
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
self.rt.block_on(self.db.basic_async_ref(address))
}

#[inline]
fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.rt.block_on(self.db.code_by_hash_async_ref(code_hash))
}

#[inline]
fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
self.rt.block_on(self.db.storage_async_ref(address, index))
}

#[inline]
fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
self.rt.block_on(self.db.block_hash_async_ref(number))
}
}

// Hold a tokio runtime handle or full runtime
#[derive(Debug)]
enum HandleOrRuntime {
Handle(Handle),
Runtime(Runtime),
}

impl HandleOrRuntime {
#[inline]
fn block_on<F>(&self, f: F) -> F::Output
where
F: Future + Send,
F::Output: Send,
{
match self {
Self::Handle(handle) => tokio::task::block_in_place(move || handle.block_on(f)),
Self::Runtime(rt) => rt.block_on(f),
}
}
}
4 changes: 4 additions & 0 deletions crates/database/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ use auto_impl::auto_impl;
use primitives::{Address, HashMap, B256, U256};
use state::{Account, AccountInfo, Bytecode};

#[cfg(feature = "asyncdb")]
pub mod async_db;
pub mod empty_db;

#[cfg(feature = "asyncdb")]
pub use async_db::{DatabaseAsync, WrapDatabaseAsync};
pub use empty_db::{EmptyDB, EmptyDBTyped};

/// EVM database interface.
Expand Down
Loading

0 comments on commit 411d0ef

Please sign in to comment.