feat: use helios as a library (#85)

* add root helios package

* fix revm

* copy blocktag when passing to funcs

* run all tests

* update readme

* update readme

* update readme
This commit is contained in:
Noah Citron 2022-11-03 19:36:14 -04:00 committed by GitHub
parent f605f009a7
commit ba08cc1a3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 560 additions and 303 deletions

View File

@ -21,7 +21,7 @@ jobs:
run: rustup target add aarch64-apple-darwin
- name: build
run: cargo build --all --release --target aarch64-apple-darwin
run: cargo build --package cli --release --target aarch64-apple-darwin
- name: archive
run: gtar -czvf "helios_darwin_arm64.tar.gz" -C ./target/aarch64-apple-darwin/release helios
@ -58,7 +58,7 @@ jobs:
run: rustup target add x86_64-apple-darwin
- name: build
run: cargo build --all --release --target x86_64-apple-darwin
run: cargo build --package cli --release --target x86_64-apple-darwin
- name: archive
run: gtar -czvf "helios_darwin_amd64.tar.gz" -C ./target/x86_64-apple-darwin/release helios
@ -101,7 +101,7 @@ jobs:
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: build
run: cargo build --all --release --target aarch64-unknown-linux-gnu
run: cargo build --package cli --release --target aarch64-unknown-linux-gnu
- name: archive
run: tar -czvf "helios_linux_arm64.tar.gz" -C ./target/aarch64-unknown-linux-gnu/release helios
@ -138,7 +138,7 @@ jobs:
run: rustup target add x86_64-unknown-linux-gnu
- name: build
run: cargo build --all --release --target x86_64-unknown-linux-gnu
run: cargo build --package cli --release --target x86_64-unknown-linux-gnu
- name: archive
run: tar -czvf "helios_linux_amd64.tar.gz" -C ./target/x86_64-unknown-linux-gnu/release helios

View File

@ -34,6 +34,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: test
args: --all
fmt:
name: fmt

600
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,8 @@
[package]
name = "helios"
version = "0.0.1"
edition = "2021"
[workspace]
members = [
@ -9,5 +14,13 @@ members = [
"execution",
]
[dependencies]
client = { path = "./client" }
config = { path = "./config" }
common = { path = "./common" }
consensus = { path = "./consensus" }
execution = { path = "./execution" }
[patch.crates-io]
ethers = { git = "https://github.com/ncitron/ethers-rs", branch = "fix-retry" }

View File

@ -28,6 +28,8 @@ Helios will now run a local RPC server at `http://127.0.0.1:8545`.
`--rpc-port` or `-p` sets the port that the local RPC should run on. The default value is `8545`.
`--data-dir` or `d` sets the directory that Helios should use to store cached weak subjectivity checkpoints in. Each network only stores the latest checkpoint, which is just 32 bytes.
### Configuration Files
All configuration options can be set on a per-network level in `~/.helios/helios.toml`. Here is an example config file:
```
@ -42,5 +44,39 @@ execution_rpc = "https://eth-goerli.g.alchemy.com/v2/XXXXX"
checkpoint = "0xb5c375696913865d7c0e166d87bc7c772b6210dc9edf149f4c7ddc6da0dd4495"
```
### Using Helios as a Library
Helios can be imported into any Rust project. Helios requires the Rust nightly toolchain to compile.
```rust
use std::{str::FromStr, env};
use helios::{client::ClientBuilder, config::networks::Network, types::BlockTag};
use ethers::{types::Address, utils};
use eyre::Result;
#[tokio::main]
async fn main() -> Result<()> {
let untrusted_rpc_url = env::var("UNTRUSTED_RPC_URL")?;
let mut client = ClientBuilder::new()
.network(Network::MAINNET)
.consensus_rpc("https://www.lightclientdata.org")
.execution_rpc(&untrusted_rpc_url)
.build()?;
client.start().await?;
let head_block_num = client.get_block_number().await?;
let addr = Address::from_str("0x00000000219ab540356cBB839Cbe05303d7705Fa")?;
let block = BlockTag::Latest;
let balance = client.get_balance(&addr, block).await?;
println!("synced up to block: {}", head_block_num);
println!("balance of deposit contract: {}", utils::format_ether(balance));
Ok(())
}
```
## Contributing
All contributions to Helios are welcome. Before opening a PR, please submit an issue detailing the bug or feature. When opening a PR, please ensure that your contribution builds on the nightly rust toolchain, has been linted with `cargo fmt`, and contains tests when applicable.

View File

@ -1,8 +1,15 @@
cargo-features = ["different-binary-name"]
[package]
name = "helios"
name = "cli"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "cli"
filename = "helios"
path = "src/main.rs"
[dependencies]
tokio = { version = "1", features = ["full"] }
clap = { version = "3.2.18", features = ["derive", "env"] }

View File

@ -15,7 +15,7 @@ ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1
blst = "0.3.10"
ethers = "1.0.0"
jsonrpsee = { version = "0.15.1", features = ["full"] }
revm = "1.9.0"
revm = "2.1.0"
bytes = "1.2.1"
futures = "0.3.23"
toml = "0.5.9"

View File

@ -22,7 +22,7 @@ use crate::rpc::Rpc;
pub struct Client<DB: Database> {
node: Arc<RwLock<Node>>,
rpc: Option<Rpc>,
db: DB,
db: Option<DB>,
}
impl Client<FileDB> {
@ -38,7 +38,11 @@ impl Client<FileDB> {
};
let data_dir = config.data_dir.clone();
let db = FileDB::new(data_dir.ok_or(eyre!("data dir not found"))?);
let db = if let Some(dir) = data_dir {
Some(FileDB::new(dir))
} else {
None
};
Ok(Client { node, rpc, db })
}
@ -114,29 +118,29 @@ impl ClientBuilder {
config.to_base_config()
};
let consensus_rpc = self.consensus_rpc.unwrap_or(
let consensus_rpc = self.consensus_rpc.unwrap_or_else(|| {
self.config
.as_ref()
.ok_or(eyre!("missing consensus rpc"))?
.expect("missing consensus rpc")
.consensus_rpc
.clone(),
);
.clone()
});
let execution_rpc = self.execution_rpc.unwrap_or(
let execution_rpc = self.execution_rpc.unwrap_or_else(|| {
self.config
.as_ref()
.ok_or(eyre!("missing execution rpc"))?
.expect("missing execution rpc")
.execution_rpc
.clone(),
);
.clone()
});
let checkpoint = self.checkpoint.unwrap_or(
self.config
.as_ref()
.ok_or(eyre!("missing checkpoint"))?
.checkpoint
.clone(),
);
let checkpoint = if let Some(checkpoint) = self.checkpoint {
checkpoint
} else if let Some(config) = &self.config {
config.checkpoint.clone()
} else {
base_config.checkpoint
};
let rpc_port = if self.rpc_port.is_some() {
self.rpc_port
@ -170,15 +174,17 @@ impl ClientBuilder {
impl<DB: Database> Client<DB> {
pub async fn start(&mut self) -> Result<()> {
self.rpc.as_mut().unwrap().start().await?;
if let Some(rpc) = &mut self.rpc {
rpc.start().await?;
}
let res = self.node.write().await.sync().await;
if let Err(err) = res {
warn!("consensus error: {}", err);
}
let node = self.node.clone();
spawn(async move {
let res = node.write().await.sync().await;
if let Err(err) = res {
warn!("consensus error: {}", err);
}
loop {
let res = node.write().await.advance().await;
if let Err(err) = res {
@ -202,13 +208,13 @@ impl<DB: Database> Client<DB> {
};
info!("saving last checkpoint hash");
let res = self.db.save_checkpoint(checkpoint);
if res.is_err() {
let res = self.db.as_ref().map(|db| db.save_checkpoint(checkpoint));
if res.is_some() && res.unwrap().is_err() {
warn!("checkpoint save failed");
}
}
pub async fn call(&self, opts: &CallOpts, block: &BlockTag) -> Result<Vec<u8>> {
pub async fn call(&self, opts: &CallOpts, block: BlockTag) -> Result<Vec<u8>> {
self.node.read().await.call(opts, block).await
}
@ -216,15 +222,15 @@ impl<DB: Database> Client<DB> {
self.node.read().await.estimate_gas(opts).await
}
pub async fn get_balance(&self, address: &Address, block: &BlockTag) -> Result<U256> {
pub async fn get_balance(&self, address: &Address, block: BlockTag) -> Result<U256> {
self.node.read().await.get_balance(address, block).await
}
pub async fn get_nonce(&self, address: &Address, block: &BlockTag) -> Result<u64> {
pub async fn get_nonce(&self, address: &Address, block: BlockTag) -> Result<u64> {
self.node.read().await.get_nonce(address, block).await
}
pub async fn get_code(&self, address: &Address, block: &BlockTag) -> Result<Vec<u8>> {
pub async fn get_code(&self, address: &Address, block: BlockTag) -> Result<Vec<u8>> {
self.node.read().await.get_code(address, block).await
}
@ -269,7 +275,7 @@ impl<DB: Database> Client<DB> {
pub async fn get_block_by_number(
&self,
block: &BlockTag,
block: BlockTag,
full_tx: bool,
) -> Result<Option<ExecutionBlock>> {
self.node

View File

@ -98,8 +98,8 @@ impl Node {
Ok(())
}
pub async fn call(&self, opts: &CallOpts, block: &BlockTag) -> Result<Vec<u8>> {
self.check_blocktag_age(block)?;
pub async fn call(&self, opts: &CallOpts, block: BlockTag) -> Result<Vec<u8>> {
self.check_blocktag_age(&block)?;
let payload = self.get_payload(block)?;
let mut evm = Evm::new(self.execution.clone(), payload.clone(), self.chain_id());
@ -109,29 +109,29 @@ impl Node {
pub async fn estimate_gas(&self, opts: &CallOpts) -> Result<u64> {
self.check_head_age()?;
let payload = self.get_payload(&BlockTag::Latest)?;
let payload = self.get_payload(BlockTag::Latest)?;
let mut evm = Evm::new(self.execution.clone(), payload.clone(), self.chain_id());
evm.estimate_gas(opts).await
}
pub async fn get_balance(&self, address: &Address, block: &BlockTag) -> Result<U256> {
self.check_blocktag_age(block)?;
pub async fn get_balance(&self, address: &Address, block: BlockTag) -> Result<U256> {
self.check_blocktag_age(&block)?;
let payload = self.get_payload(block)?;
let account = self.execution.get_account(&address, None, payload).await?;
Ok(account.balance)
}
pub async fn get_nonce(&self, address: &Address, block: &BlockTag) -> Result<u64> {
self.check_blocktag_age(block)?;
pub async fn get_nonce(&self, address: &Address, block: BlockTag) -> Result<u64> {
self.check_blocktag_age(&block)?;
let payload = self.get_payload(block)?;
let account = self.execution.get_account(&address, None, payload).await?;
Ok(account.nonce)
}
pub async fn get_code(&self, address: &Address, block: &BlockTag) -> Result<Vec<u8>> {
self.check_blocktag_age(block)?;
pub async fn get_code(&self, address: &Address, block: BlockTag) -> Result<Vec<u8>> {
self.check_blocktag_age(&block)?;
let payload = self.get_payload(block)?;
let account = self.execution.get_account(&address, None, payload).await?;
@ -141,7 +141,7 @@ impl Node {
pub async fn get_storage_at(&self, address: &Address, slot: H256) -> Result<U256> {
self.check_head_age()?;
let payload = self.get_payload(&BlockTag::Latest)?;
let payload = self.get_payload(BlockTag::Latest)?;
let account = self
.execution
.get_account(address, Some(&[slot]), payload)
@ -177,7 +177,7 @@ impl Node {
pub fn get_gas_price(&self) -> Result<U256> {
self.check_head_age()?;
let payload = self.get_payload(&BlockTag::Latest)?;
let payload = self.get_payload(BlockTag::Latest)?;
let base_fee = U256::from_little_endian(&payload.base_fee_per_gas.to_bytes_le());
let tip = U256::from(10_u64.pow(9));
Ok(base_fee + tip)
@ -192,16 +192,16 @@ impl Node {
pub fn get_block_number(&self) -> Result<u64> {
self.check_head_age()?;
let payload = self.get_payload(&BlockTag::Latest)?;
let payload = self.get_payload(BlockTag::Latest)?;
Ok(payload.block_number)
}
pub async fn get_block_by_number(
&self,
block: &BlockTag,
block: BlockTag,
full_tx: bool,
) -> Result<Option<ExecutionBlock>> {
self.check_blocktag_age(block)?;
self.check_blocktag_age(&block)?;
match self.get_payload(block) {
Ok(payload) => self
@ -247,7 +247,7 @@ impl Node {
self.consensus.last_checkpoint.clone()
}
fn get_payload(&self, block: &BlockTag) -> Result<&ExecutionPayload> {
fn get_payload(&self, block: BlockTag) -> Result<&ExecutionPayload> {
match block {
BlockTag::Latest => {
let payload = self.payloads.last_key_value();
@ -260,8 +260,8 @@ impl Node {
.1)
}
BlockTag::Number(num) => {
let payload = self.payloads.get(num);
payload.ok_or(BlockNotFoundError::new(BlockTag::Number(*num)).into())
let payload = self.payloads.get(&num);
payload.ok_or(BlockNotFoundError::new(BlockTag::Number(num)).into())
}
}
}

View File

@ -111,7 +111,7 @@ impl EthRpcServer for RpcInner {
async fn get_balance(&self, address: &str, block: BlockTag) -> Result<String, Error> {
let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await;
let balance = convert_err(node.get_balance(&address, &block).await)?;
let balance = convert_err(node.get_balance(&address, block).await)?;
Ok(balance.encode_hex())
}
@ -119,7 +119,7 @@ impl EthRpcServer for RpcInner {
async fn get_transaction_count(&self, address: &str, block: BlockTag) -> Result<String, Error> {
let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await;
let nonce = convert_err(node.get_nonce(&address, &block).await)?;
let nonce = convert_err(node.get_nonce(&address, block).await)?;
Ok(nonce.encode_hex())
}
@ -127,14 +127,14 @@ impl EthRpcServer for RpcInner {
async fn get_code(&self, address: &str, block: BlockTag) -> Result<String, Error> {
let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await;
let code = convert_err(node.get_code(&address, &block).await)?;
let code = convert_err(node.get_code(&address, block).await)?;
Ok(hex::encode(code))
}
async fn call(&self, opts: CallOpts, block: BlockTag) -> Result<String, Error> {
let node = self.node.read().await;
let res = convert_err(node.call(&opts, &block).await)?;
let res = convert_err(node.call(&opts, block).await)?;
Ok(format!("0x{}", hex::encode(res)))
}
@ -176,7 +176,7 @@ impl EthRpcServer for RpcInner {
full_tx: bool,
) -> Result<Option<ExecutionBlock>, Error> {
let node = self.node.read().await;
let block = convert_err(node.get_block_by_number(&block, full_tx).await)?;
let block = convert_err(node.get_block_by_number(block, full_tx).await)?;
Ok(block)
}

View File

@ -5,7 +5,7 @@ use ssz_rs::Vector;
pub type Bytes32 = Vector<u8, 32>;
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub enum BlockTag {
Latest,
Finalized,

View File

@ -15,7 +15,7 @@ hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" }
blst = "0.3.10"
ethers = "1.0.0"
revm = "1.9.0"
revm = "2.1.0"
bytes = "1.2.1"
futures = "0.3.23"
toml = "0.5.9"

View File

@ -1,4 +1,4 @@
use std::{cmp, collections::HashMap, fmt::Display, str::FromStr, sync::Arc, thread};
use std::{cmp, collections::HashMap, str::FromStr, sync::Arc, thread};
use bytes::Bytes;
use ethers::{
@ -6,7 +6,7 @@ use ethers::{
prelude::{Address, H160, H256, U256},
types::transaction::eip2930::AccessListItem,
};
use eyre::Result;
use eyre::{Report, Result};
use futures::future::join_all;
use log::trace;
use revm::{AccountInfo, Bytecode, Database, Env, TransactOut, TransactTo, EVM};
@ -44,10 +44,9 @@ impl<R: ExecutionRpc> Evm<R> {
self.evm.db.as_mut().unwrap().set_accounts(account_map);
self.evm.env = self.get_env(opts);
let tx = self.evm.transact();
let output = tx.1;
let tx = self.evm.transact().0;
match tx.0 {
match tx.exit_reason {
revm::Return::Revert => Err(eyre::eyre!("execution reverted")),
revm::Return::OutOfGas => Err(eyre::eyre!("execution reverted: out of gas")),
revm::Return::OutOfFund => Err(eyre::eyre!("not enough funds")),
@ -63,7 +62,7 @@ impl<R: ExecutionRpc> Evm<R> {
return Err(eyre::eyre!(err.clone()));
}
match output {
match tx.out {
TransactOut::None => Err(eyre::eyre!("Invalid Call")),
TransactOut::Create(..) => Err(eyre::eyre!("Invalid Call")),
TransactOut::Call(bytes) => Ok(bytes.to_vec()),
@ -77,10 +76,10 @@ impl<R: ExecutionRpc> Evm<R> {
self.evm.db.as_mut().unwrap().set_accounts(account_map);
self.evm.env = self.get_env(opts);
let tx = self.evm.transact();
let gas = tx.2;
let tx = self.evm.transact().0;
let gas = tx.gas_used;
match tx.0 {
match tx.exit_reason {
revm::Return::Revert => Err(eyre::eyre!("execution reverted")),
revm::Return::OutOfGas => Err(eyre::eyre!("execution reverted: out of gas")),
revm::Return::OutOfFund => Err(eyre::eyre!("not enough funds")),
@ -211,21 +210,11 @@ impl<R: ExecutionRpc> ProofDB<R> {
}
}
pub fn safe_unwrap<T: Default, E: Display>(&mut self, res: Result<T, E>) -> T {
match res {
Ok(value) => value,
Err(err) => {
self.error = Some(err.to_string());
T::default()
}
}
}
pub fn set_accounts(&mut self, accounts: HashMap<Address, Account>) {
self.accounts = accounts;
}
fn get_account(&mut self, address: Address, slots: &[H256]) -> Account {
fn get_account(&mut self, address: Address, slots: &[H256]) -> Result<Account> {
let execution = self.execution.clone();
let addr = address.clone();
let payload = self.payload.clone();
@ -237,14 +226,16 @@ impl<R: ExecutionRpc> ProofDB<R> {
runtime.block_on(account_fut)
});
self.safe_unwrap(handle.join().unwrap())
handle.join().unwrap()
}
}
impl<R: ExecutionRpc> Database for ProofDB<R> {
fn basic(&mut self, address: H160) -> AccountInfo {
type Error = Report;
fn basic(&mut self, address: H160) -> Result<Option<AccountInfo>, Report> {
if is_precompile(&address) {
return AccountInfo::default();
return Ok(Some(AccountInfo::default()));
}
trace!(
@ -254,18 +245,22 @@ impl<R: ExecutionRpc> Database for ProofDB<R> {
let account = match self.accounts.get(&address) {
Some(account) => account.clone(),
None => self.get_account(address, &[]),
None => self.get_account(address, &[])?,
};
let bytecode = Bytecode::new_raw(Bytes::from(account.code.clone()));
AccountInfo::new(account.balance, account.nonce, bytecode)
Ok(Some(AccountInfo::new(
account.balance,
account.nonce,
bytecode,
)))
}
fn block_hash(&mut self, _number: U256) -> H256 {
H256::default()
fn block_hash(&mut self, _number: U256) -> Result<H256, Report> {
Ok(H256::default())
}
fn storage(&mut self, address: H160, slot: U256) -> U256 {
fn storage(&mut self, address: H160, slot: U256) -> Result<U256, Report> {
trace!(
"fetch evm state for address=0x{}, slot={}",
hex::encode(address.as_bytes()),
@ -274,27 +269,27 @@ impl<R: ExecutionRpc> Database for ProofDB<R> {
let slot = H256::from_uint(&slot);
match self.accounts.get(&address) {
Ok(match self.accounts.get(&address) {
Some(account) => match account.slots.get(&slot) {
Some(slot) => slot.clone(),
None => self
.get_account(address, &[slot])
.get_account(address, &[slot])?
.slots
.get(&slot)
.unwrap()
.clone(),
},
None => self
.get_account(address, &[slot])
.get_account(address, &[slot])?
.slots
.get(&slot)
.unwrap()
.clone(),
}
})
}
fn code_by_hash(&mut self, _code_hash: H256) -> Bytecode {
panic!("should never be called");
fn code_by_hash(&mut self, _code_hash: H256) -> Result<Bytecode, Report> {
Err(eyre::eyre!("should never be called"))
}
}

17
src/lib.rs Normal file
View File

@ -0,0 +1,17 @@
pub mod client {
pub use client::{database::FileDB, Client, ClientBuilder};
}
pub mod config {
pub use config::{networks, Config};
}
pub mod types {
pub use common::types::BlockTag;
}
pub mod errors {
pub use common::errors::*;
pub use consensus::errors::*;
pub use execution::errors::*;
}