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(arbiter-core): db and coprocessor backend #744

Merged
merged 8 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
970 changes: 576 additions & 394 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ name = "arbiter"
path = "bin/main.rs"

[workspace.dependencies]
ethers = { version = "=2.0.10" }
serde = { version = "=1.0.192", features = ["derive"] }
ethers = { version = "2.0.11" }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = { version = "=1.0.108" }
revm = { version = "=3.5.0", features = [ "ethersdb", "std", "serde"] }
revm-primitives = { version = "1.3.0" }
thiserror = { version = "=1.0.50" }
syn = { version = "=2.0.39" }
revm = { git = "https://github.com/bluealloy/revm.git", features = [ "ethersdb", "std", "serde"] }
revm-primitives = { git = "https://github.com/bluealloy/revm.git" }
thiserror = { version = "1.0.51" }
syn = { version = "2.0.42" }
quote = { version = "=1.0.33" }
proc-macro2 = { version = "=1.0.69" }
tokio = { version = "1.35.1", features = ["macros", "full"] }
proc-macro2 = { version = "1.0.70" }
tokio = { version = "1.35.0", features = ["macros", "full"] }
arbiter-core = { path = "./arbiter-core" }
crossbeam-channel = { version = "=0.5.10" }
futures-util = { version = "=0.3.29" }
crossbeam-channel = { version = "0.5.9" }
futures-util = { version = "=0.3.30" }
async-trait = { version = "0.1.76" }
tracing = "0.1.40"

Expand Down
5 changes: 3 additions & 2 deletions arbiter-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ readme = "../README.md"
ethers.workspace = true
revm.workspace = true
revm-primitives.workspace = true
hashbrown = "0.14.3"

# Serialization
bytes = { version = "=1.5.0" }
Expand All @@ -23,7 +24,7 @@ serde_json.workspace = true
# Concurrency/async
tokio.workspace = true
async-trait.workspace = true
crossbeam-channel = { version = "=0.5.10" }
crossbeam-channel.workspace = true
futures-timer = { version = "=3.0.2" }
futures-locks = { version = "=0.7.1" }

Expand All @@ -47,7 +48,7 @@ polars = { version = "0.35.4", features = ["parquet", "csv", "json"] }
arbiter-derive = { path = "../arbiter-derive" }
arbiter-bindings = { path = "../arbiter-bindings" }
hex = { version = "=0.4.3", default-features = false }
anyhow = { version = "=1.0.78" }
anyhow = { version = "=1.0.79" }
test-log = { version = "=0.2.14" }
tracing-test = "0.2.4"

Expand Down
69 changes: 69 additions & 0 deletions arbiter-core/src/coprocessor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! The `Coprocessor` is used to process calls and can access read-only from the
//! `Environment`'s database. The `Coprocessor` will stay up to date with the
//! latest state of the `Environment`'s database.

use std::convert::Infallible;

use revm::EVM;
use revm_primitives::{EVMError, ResultAndState};

use crate::{database::ArbiterDB, environment::Environment};

// TODO: I have not tested that the coprocessor actually maintains state parity
// with the environment. At the moment, it is only able to be constructed and
// can certainly act as a read-only EVM.

/// A `Coprocessor` is used to process calls and can access read-only from the
/// `Environment`'s database. This can eventually be used for things like
/// parallelized compute for agents that are not currently sending transactions
/// that need to be processed by the `Environment`, but are instead using the
/// current state to make decisions.
pub struct Coprocessor {
evm: EVM<ArbiterDB>,
}

impl Coprocessor {
/// Create a new `Coprocessor` with the given `Environment`.
pub fn new(environment: &Environment) -> Self {
let db = ArbiterDB(
environment
.db
.as_ref()
.unwrap_or(&ArbiterDB::new())
.0
.clone(),
);
let mut evm = EVM::new();
evm.database(db);
Self { evm }
}

/// Used as an entrypoint to process a call with the `Coprocessor`.
pub fn transact_ref(&self) -> Result<ResultAndState, EVMError<Infallible>> {
self.evm.transact_ref()
}
}

#[cfg(test)]
mod tests {
use revm_primitives::{InvalidTransaction, U256};

use super::*;
use crate::environment::builder::EnvironmentBuilder;

#[test]
fn coprocessor() {
let environment = EnvironmentBuilder::new().build();
let mut coprocessor = Coprocessor::new(&environment);
coprocessor.evm.env.tx.value = U256::from(100);
let outcome = coprocessor.transact_ref();
if let Err(EVMError::Transaction(InvalidTransaction::LackOfFundForMaxFee {
fee,
balance,
})) = outcome
{
assert_eq!(*fee, U256::from(100));
assert_eq!(*balance, U256::from(0));
}
}
}
146 changes: 146 additions & 0 deletions arbiter-core/src/database.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! The `ArbiterDB` is a wrapper around a `CacheDB` that is used to provide
//! access to the `Environment`'s database to multiple `Coprocessors`.
//! It is also used to be able to write out the `Environment` database to a
//! file.

use std::{
convert::Infallible,
fmt::Debug,
fs,
io::{self, Read, Write},
sync::{Arc, RwLock},
};

use revm::{
db::{CacheDB, EmptyDB},
primitives::{AccountInfo, B256, U256},
Database, DatabaseCommit,
};
use revm_primitives::{db::DatabaseRef, Bytecode};
use serde::{Deserialize, Serialize};
use serde_json;

/// A `ArbiterDB` is a wrapper around a `CacheDB` that is used to provide
/// access to the `Environment`'s database to multiple `Coprocessors`.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ArbiterDB(pub(crate) Arc<RwLock<CacheDB<EmptyDB>>>);

impl ArbiterDB {
/// Create a new `ArbiterDB`.
pub fn new() -> Self {
Self(Arc::new(RwLock::new(CacheDB::new(EmptyDB::new()))))
}

/// Write the `ArbiterDB` to a file at the given path.
pub fn write_to_file(&self, path: &str) -> io::Result<()> {
// Serialize the ArbiterDB
let serialized = serde_json::to_string(self)?;
// Write to file
let mut file = fs::File::create(path)?;
file.write_all(serialized.as_bytes())?;
Ok(())
}

/// Read the `ArbiterDB` from a file at the given path.
pub fn read_from_file(path: &str) -> io::Result<Self> {
// Read the file content
let mut file = fs::File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
// Deserialize the content into ArbiterDB
let cache_db = serde_json::from_str(&contents)?;
Ok(Self(Arc::new(RwLock::new(cache_db))))
}
}

impl Default for ArbiterDB {
fn default() -> Self {
Self::new()
}
}

// TODO: This is a BAD implementation of PartialEq, but it works for now as we
// do not ever really need to compare DBs directly at the moment.
// This is only used in the `Outcome` enum for `instruction.rs`.
impl PartialEq for ArbiterDB {
fn eq(&self, _other: &Self) -> bool {
true
}
}

impl Database for ArbiterDB {
type Error = Infallible; // TODO: Not sure we want this, but it works for now.

fn basic(
&mut self,
address: revm::primitives::Address,
) -> Result<Option<AccountInfo>, Self::Error> {
self.0.write().unwrap().basic(address)
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.0.write().unwrap().code_by_hash(code_hash)
}

fn storage(
&mut self,
address: revm::primitives::Address,
index: U256,
) -> Result<U256, Self::Error> {
self.0.write().unwrap().storage(address, index)
}

fn block_hash(&mut self, number: U256) -> Result<B256, Self::Error> {
self.0.write().unwrap().block_hash(number)
}
}

impl DatabaseRef for ArbiterDB {
type Error = Infallible; // TODO: Not sure we want this, but it works for now.

fn basic_ref(
&self,
address: revm::primitives::Address,
) -> Result<Option<AccountInfo>, Self::Error> {
self.0.read().unwrap().basic_ref(address)
}

fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.0.read().unwrap().code_by_hash_ref(code_hash)
}

fn storage_ref(
&self,
address: revm::primitives::Address,
index: U256,
) -> Result<U256, Self::Error> {
self.0.read().unwrap().storage_ref(address, index)
}

fn block_hash_ref(&self, number: U256) -> Result<B256, Self::Error> {
self.0.read().unwrap().block_hash_ref(number)
}
}

impl DatabaseCommit for ArbiterDB {
fn commit(
&mut self,
changes: hashbrown::HashMap<revm::primitives::Address, revm::primitives::Account>,
) {
self.0.write().unwrap().commit(changes)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn read_write_to_file() {
let db = ArbiterDB::new();
db.write_to_file("test.json").unwrap();
let db = ArbiterDB::read_from_file("test.json").unwrap();
assert_eq!(db, ArbiterDB::new());
fs::remove_file("test.json").unwrap();
}
}
2 changes: 0 additions & 2 deletions arbiter-core/src/environment/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
//! [`EnvironmentParameters`] structures as well as the [`BlockSettings`] and
//! [`GasSettings`] enums.

#![warn(missing_docs)]

use super::*;

/// Parameters necessary for creating or modifying an `Environment`.
Expand Down
2 changes: 0 additions & 2 deletions arbiter-core/src/environment/cheatcodes.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Cheatcodes are a direct way to access the underlying [`EVM`] environment
// and database. ! Use them via the `apply_cheatcode` method on a `client`.

#![warn(missing_docs)]

use revm_primitives::{AccountInfo, HashMap, U256};

/// Cheatcodes are a direct way to access the underlying [`EVM`] environment and
Expand Down
2 changes: 0 additions & 2 deletions arbiter-core/src/environment/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Errors that can occur when managing or interfacing with Arbiter's sandboxed
//! Ethereum environment.

#![warn(missing_docs)]

use super::*;

/// Errors that can occur when managing or interfacing with the Ethereum
Expand Down
4 changes: 1 addition & 3 deletions arbiter-core/src/environment/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![warn(missing_docs)]

use super::*;

/// [`Instruction`]s that can be sent to the [`Environment`] via the
Expand Down Expand Up @@ -135,7 +133,7 @@ pub(crate) enum Outcome {

/// The outcome of a `Stop` instruction that is used to signify that the
/// [`Environment`] was stopped successfully.
StopCompleted,
StopCompleted(ArbiterDB),
}

/// [`EnvironmentData`] is an enum used inside of the [`Instruction::Query`] to
Expand Down
Loading
Loading