Skip to content

Commit

Permalink
refactor(#528): deconstructs middleware module into components
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexangelj committed Sep 28, 2023
1 parent 508fe42 commit b30f727
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 297 deletions.
56 changes: 56 additions & 0 deletions arbiter-core/src/middleware/cast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
///! Utility functions for casting between revm and ethers-rs types.
use ethers::types::Address;
use revm::primitives::B160;

/// Converts logs from the Revm format to the Ethers format.
///
/// This function iterates over a list of logs as they appear in the `revm` and
/// converts each log entry to the corresponding format used by the `ethers-rs`
/// library.
#[inline]
pub fn revm_logs_to_ethers_logs(
revm_logs: Vec<revm::primitives::Log>,
) -> Vec<ethers::core::types::Log> {
let mut logs: Vec<ethers::core::types::Log> = vec![];
for revm_log in revm_logs {
let topics = revm_log.topics.into_iter().map(recast_b256).collect();
let log = ethers::core::types::Log {
address: recast_address(revm_log.address),
topics,
data: ethers::core::types::Bytes::from(revm_log.data),
block_hash: None,
block_number: None,
transaction_hash: None,
transaction_index: None,
log_index: None,
transaction_log_index: None,
log_type: None,
removed: None,
};
logs.push(log);
}
logs
}

// Certainly will go away with alloy-types
/// Recast a B160 into an Address type
/// # Arguments
/// * `address` - B160 to recast. (B160)
/// # Returns
/// * `Address` - Recasted Address.
#[inline]
pub fn recast_address(address: B160) -> Address {
let temp: [u8; 20] = address.as_bytes().try_into().unwrap();
Address::from(temp)
}

/// Recast a B256 into an H256 type
/// # Arguments
/// * `input` - B256 to recast. (B256)
/// # Returns
/// * `H256` - Recasted H256.
#[inline]
pub fn recast_b256(input: revm::primitives::B256) -> ethers::types::H256 {
let temp: [u8; 32] = input.as_bytes().try_into().unwrap();
ethers::types::H256::from(temp)
}
109 changes: 109 additions & 0 deletions arbiter-core/src/middleware/connections.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
///! Messengers/connections to the underlying EVM in the environment.
use std::{
collections::HashMap,
fmt::Debug,
sync::{Arc, Mutex, Weak},
};

use ethers::{prelude::ProviderError, providers::JsonRpcClient, types::FilteredParams};
use serde::{de::DeserializeOwned, Serialize};

use super::cast::revm_logs_to_ethers_logs;
use super::events::FilterReceiver;

use crate::environment::{EventBroadcaster, InstructionSender, OutcomeReceiver, OutcomeSender};

/// Represents a connection to the EVM contained in the corresponding
/// [`Environment`].
#[derive(Debug)]
pub struct Connection {
/// Used to send calls and transactions to the [`Environment`] to be
/// executed by `revm`.
pub(crate) instruction_sender: Weak<InstructionSender>,

/// Used to send results back to a client that made a call/transaction with
/// the [`Environment`]. This [`ResultSender`] is passed along with a
/// call/transaction so the [`Environment`] can reply back with the
/// [`ExecutionResult`].
pub(crate) outcome_sender: OutcomeSender,

/// Used to receive the [`ExecutionResult`] from the [`Environment`] upon
/// call/transact.
pub(crate) outcome_receiver: OutcomeReceiver,

/// A reference to the [`EventBroadcaster`] so that more receivers of the
/// broadcast can be taken from it.
pub(crate) event_broadcaster: Arc<Mutex<EventBroadcaster>>,

/// A collection of `FilterReceiver`s that will receive outgoing logs
/// generated by `revm` and output by the [`Environment`].
pub(crate) filter_receivers:
Arc<tokio::sync::Mutex<HashMap<ethers::types::U256, FilterReceiver>>>,
}

#[async_trait::async_trait]
impl JsonRpcClient for Connection {
type Error = ProviderError;

/// Processes a JSON-RPC request and returns the response.
/// Currently only handles the `eth_getFilterChanges` call since this is
/// used for polling events emitted from the [`Environment`].
async fn request<T: Serialize + Send + Sync, R: DeserializeOwned>(
&self,
method: &str,
params: T,
) -> Result<R, ProviderError> {
match method {
"eth_getFilterChanges" => {
// TODO: The extra json serialization/deserialization can probably be avoided
// somehow

// Get the `Filter` ID from the params `T`
// First convert it into a JSON `Value`
let value = serde_json::to_value(&params)?;

// Take this value as an array then cast it to a string
let str = value.as_array().ok_or(ProviderError::CustomError(
"The params value passed to the `Connection` via a `request` was empty.
This is likely due to not specifying a specific `Filter` ID!".to_string()
))?[0]
.as_str().ok_or(ProviderError::CustomError(
"The params value passed to the `Connection` via a `request` could not be later cast to `str`!".to_string()
))?;

// Now get the `U256` ID via the string decoded from hex radix.
let id = ethers::types::U256::from_str_radix(str, 16)
.map_err(|e| ProviderError::CustomError(
format!("The `str` representation of the filter ID could not be cast into `U256` due to: {:?}!",
e)))?;

// Get the corresponding `filter_receiver` and await for logs to appear.
let mut filter_receivers = self.filter_receivers.lock().await;
let filter_receiver =
filter_receivers
.get_mut(&id)
.ok_or(ProviderError::CustomError(
"The filter ID does not seem to match any that this client owns!"
.to_string(),
))?;
let mut logs = vec![];
let filtered_params = FilteredParams::new(Some(filter_receiver.filter.clone()));
if let Ok(received_logs) = filter_receiver.receiver.try_recv() {
let ethers_logs = revm_logs_to_ethers_logs(received_logs);
for log in ethers_logs {
if filtered_params.filter_address(&log)
&& filtered_params.filter_topics(&log)
{
logs.push(log);
}
}
}
// Take the logs and Stringify then JSONify to cast into `R`.
let logs_str = serde_json::to_string(&logs)?;
let logs_deserializeowned: R = serde_json::from_str(&logs_str)?;
Ok(logs_deserializeowned)
}
_ => Err(ProviderError::UnsupportedRPC),
}
}
}
86 changes: 86 additions & 0 deletions arbiter-core/src/middleware/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
///! Possible errors thrown by interacting with the revm middleware client.
use ethers::{prelude::ProviderError, providers::MiddlewareError};
use std::fmt::Debug;
use thiserror::Error;

/// Errors that can occur while using the [`RevmMiddleware`].
/// These errors are likely to be more common than other errors in
/// `arbiter-core` as they can come from simple issues such as contract reverts
/// or halts. Certain errors such as [`RevmMiddlewareError::Send`],
/// [`RevmMiddlewareError::Receive`], [`RevmMiddlewareError::Conversion`],
/// [`RevmMiddlewareError::Json`], and [`RevmMiddlewareError::EventBroadcaster`]
/// are considered more worrying. If these are achieved, please feel free to
/// contact our team via the [Telegram group](https://t.me/arbiter_rs) or on
/// [GitHub](https://github.com/primitivefinance/arbiter/).
#[derive(Error, Debug)]
pub enum RevmMiddlewareError {
/// An error occurred while attempting to interact with the [`Environment`].
#[error("an error came from the environment! due to: {0}")]
Environment(#[from] crate::environment::errors::EnvironmentError),

/// An error occurred while attempting to interact with the provider:
/// [`Connection`].
#[error("an error came from the provider! due to: {0}")]
Provider(#[from] ProviderError),

/// An error occurred while attempting to send a transaction.
#[error("failed to send transaction! due to: {0}")]
Send(String),

/// There was an issue receiving an [`ExecutionResult`], possibly from
/// another service or module.
#[error("failed to receive `ExecutionResult`! due to: {0}")]
Receive(#[from] crossbeam_channel::RecvError),

/// There was a failure trying to obtain a lock on the [`EventBroadcaster`],
/// possibly due to concurrency issues.
#[error("failed to gain event broadcaster lock! due to: {0}")]
EventBroadcaster(String),

/// The required data or functionality for an instruction was missing or
/// incomplete.
#[error("missing data! due to: {0}")]
MissingData(String),

/// An error occurred during type conversion, possibly when translating
/// between domain-specific types.
#[error("failed to convert types! due to: {0}")]
Conversion(String),

/// An error occurred while trying to serialize or deserialize JSON data.
#[error("failed to handle with JSON data! due to: {0:?}")]
Json(serde_json::Error),

/// The execution of a transaction was reverted, indicating that the
/// transaction was not successful.
#[error("execution failed to succeed due to revert!\n gas used is: {gas_used}\n output is {output:?}")]
ExecutionRevert {
/// Provides the amount of gas used by the transaction.
gas_used: u64,

/// Provides the output or reason why the transaction was reverted.
output: revm::primitives::Bytes,
},

/// The execution of a transaction halted unexpectedly.
#[error("execution failed to succeed due to halt!\n reason is: {reason:?}\n gas used is: {gas_used}")]
ExecutionHalt {
/// Provides the reason for the halt.
reason: revm::primitives::Halt,

/// Provides the amount of gas used by the transaction.
gas_used: u64,
},
}

impl MiddlewareError for RevmMiddlewareError {
type Inner = ProviderError;

fn from_err(e: Self::Inner) -> Self {
RevmMiddlewareError::Provider(e)
}

fn as_inner(&self) -> Option<&Self::Inner> {
None
}
}
18 changes: 18 additions & 0 deletions arbiter-core/src/middleware/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
///! Specific event types that can be emitted by the [`Environment`].
///
use ethers::types::Filter;
use std::fmt::Debug;

/// Packages together a [`crossbeam_channel::Receiver<Vec<Log>>`] along with a
/// [`Filter`] for events. Allows the client to have a stream of filtered
/// events.
#[derive(Debug)]
pub(crate) struct FilterReceiver {
/// The filter definition used for this receiver.
/// Comes from the `ethers-rs` crate.
pub(crate) filter: Filter,

/// The receiver for the channel that receives logs from the broadcaster.
/// These are filtered upon reception.
pub(crate) receiver: crossbeam_channel::Receiver<Vec<revm::primitives::Log>>,
}
Loading

0 comments on commit b30f727

Please sign in to comment.