Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(db): SyncDatabase for cross-chain state access #2

Closed
wants to merge 134 commits into from

Conversation

CeciliaZ030
Copy link

@CeciliaZ030 CeciliaZ030 commented Oct 2, 2024

should merge after #1, this is the same PR as taikoxyz#44

Overview

Screenshot 2024-10-02 at 2 53 41 AM

We modified Revm for cross-chain state access, namely, it reads any account/storage with ChainAddress = (ChainId, Address) and we need to support that when we start L2 nodes in ExEx Context.

Current Database & Provider

Screenshot 2024-10-02 at 2 53 23 AM

Revm

Need state provider associated with the current block to execute transactions, so the entire Revm crate abstracts over DB: revm::Database to provide this access. The struct implementation is typically a wrapper holding an in-memory cache state with underlying real database passed from Reth.

pub trait Database {
    /// The database error type.
    type Error;

    /// Get basic account information.
    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error>;

    /// Get account code by its hash.
    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error>;

    /// Get storage value of address at index.
    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error>;

    /// Get block hash by block number.
    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error>;
}

Reth

Reth uses a ProviderFactory that holds unique references to an in-memory (mostly MDBX) DB and a StaticFileProvider: https://github.com/paradigmxyz/reth/tree/main/crates/static-file/static-file
The lowest level abstraction of Database in Reth represents operations on reading/writing data, which is different from that of Revm. Between the physical database and Revm state provider, Reth implemented many APIs to access blockchain data and they are mostly in StateProvider trait.

/// Main Database trait that can open read-only and read-write transactions.
///
/// Sealed trait which cannot be implemented by 3rd parties, exposed only for consumption.
pub trait Database: Send + Sync {
    /// Read-Only database transaction
    type TX: DbTx + Send + Sync + Debug + 'static;
    /// Read-Write database transaction
    type TXMut: DbTxMut + DbTx + TableImporter + Send + Sync + Debug + 'static;

    /// Create read only transaction.
    #[track_caller]
    fn tx(&self) -> Result<Self::TX, DatabaseError>;

    /// Create read write transaction only possible if database is open with write access.
    #[track_caller]
    fn tx_mut(&self) -> Result<Self::TXMut, DatabaseError>;
    ... ...
}

Cross-chain Database & Provider

We first modified Revm's Database trait to execute transactions against the targeted state root: taikoxyz/revm#17

pub trait SyncDatabase {
    /// The database error type.
    type Error;

    /// Get basic account information.
    fn basic(&mut self, address: ChainAddress) -> Result<Option<AccountInfo>, Self::Error>;

    /// Get account code by its hash.
    fn code_by_hash(&mut self, chain_id: u64, code_hash: B256) -> Result<Bytecode, Self::Error>;

    /// Get storage value of address at index.
    fn storage(&mut self, address: ChainAddress, index: U256) -> Result<U256, Self::Error>;

    /// Get block hash by block number.
    fn block_hash(&mut self, chain_id: u64, number: u64) -> Result<B256, Self::Error>;
}

For Reth, the cross-chain state provider should be implemented as a HashMap over some database trait, but the concrete type can be anything. Thus, performing type erasure with dyn trait object makes sense. We managed to preserve Reth's abstraction on its physical database, and the building process of different generic Box< dyn StateProvider> is kept the same. The main DB being pass into the executor is the following:

pub struct SyncStateProviderDatabase<DB>(pub HashMap<u64, StateProviderDatabase<DB>>);

This implements SyncDatabase and a modified blanket trait SyncEvmStateProvider which glue the old singleton database with EvmStateProvider and the synchronous database together:

impl<DB: EvmStateProvider> SyncEvmStateProvider for SyncStateProviderDatabase<DB> {
    fn basic_account(&self, address: ChainAddress) -> ProviderResult<Option<Account>> {
        if let Some(db) = self.get(&address.0) {
            db.0.basic_account(address.1)
        } else {
            Err(ProviderError::UnsupportedProvider)
        }
    }
    // ... other functions ...
}

We can start the sync DB compatible with the old code, and insert as many DB instances for multiple L2s in the ExEx context. Note that the CacheDB and state builder with WrapDatabaseRef are also available out of the box:
https://github.com/taikoxyz/taiko-reth/blob/1f7ea4e2ce3b55b11e6b98fa94a1ffe3a998a556/crates/gwyneth/src/builder.rs#L80
Screenshot 2024-10-02 at 8 20 20 PM

Keszey Dániel and others added 30 commits July 2, 2024 10:44
Block hash or meta hash in transition mapping ?
feat(gwyneth): add first simple tests for proposer/prover
feat(gwyneth): update everything (except core protocol) to latest taiko-mono
feat(gwyneth): add reth-based private network , and L1 contract deployment
@Brechtpd
Copy link

Merged at taikoxyz#44

@Brechtpd Brechtpd closed this Oct 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants