feat: wasm support (#182)

* basic consensus setup

* basic execution setup

* patch for wasm

* basic wasm client

* proxy cors for testing

* migrate to webpack

* use typescript

* track chain head

* rename to helios-ts

* better build instructions

* add getCode

* builds everywhere

* add wasm-pack to dependencies

* compile for both wasm and non-wasm

* fix deps

* fix deps

* remove ds store

* add blocktags

* add getNonce

* use BTreeMap to store payloads

* add getTransaction

* switch to proper ethers provider

* post merge fixes

* compile client to wasm

* fix tests

* fmt

* use milagro for bls

* handle node advance in rust

* faster bls deserialization

* clippy

* add ConfigDB

* remove ts bindings

* fix gitignore

* remove ts workspace member

* remove unused mut

* uncomment old deletions

* bump to 0.2.0
This commit is contained in:
Noah Citron 2023-01-30 21:38:46 -05:00 committed by GitHub
parent 604b325983
commit 72267b4563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 499 additions and 566 deletions

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
/target .DS_Store
target
*.env *.env

683
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "helios" name = "helios"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
autobenches = false autobenches = false
@ -21,11 +21,11 @@ common = { path = "./common" }
consensus = { path = "./consensus" } consensus = { path = "./consensus" }
execution = { path = "./execution" } execution = { path = "./execution" }
[dev-dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
eyre = "0.6.8" eyre = "0.6.8"
home = "0.5.4" home = "0.5.4"
ethers = "1.0.2" ethers = "1.0.0"
env_logger = "0.9.0" env_logger = "0.9.0"
log = "0.4.17" log = "0.4.17"
tracing-test = "0.2.3" tracing-test = "0.2.3"
@ -34,6 +34,9 @@ plotters = "0.3.3"
tempfile = "3.3.0" tempfile = "3.3.0"
hex = "0.4.3" hex = "0.4.3"
[patch.crates-io]
ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "c17c0c3c956f12d205a5ede3176599d8a30ca739" }
[profile.release] [profile.release]
strip = true strip = true
opt-level = "z" opt-level = "z"

View File

@ -2,7 +2,7 @@ cargo-features = ["different-binary-name"]
[package] [package]
name = "cli" name = "cli"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[[bin]] [[bin]]

View File

@ -1,5 +1,4 @@
use std::{ use std::{
fs,
path::PathBuf, path::PathBuf,
process::exit, process::exit,
str::FromStr, str::FromStr,
@ -104,10 +103,10 @@ struct Cli {
impl Cli { impl Cli {
fn as_cli_config(&self) -> CliConfig { fn as_cli_config(&self) -> CliConfig {
let checkpoint = match &self.checkpoint { let checkpoint = self
Some(checkpoint) => Some(hex_str_to_bytes(checkpoint).expect("invalid checkpoint")), .checkpoint
None => self.get_cached_checkpoint(), .as_ref()
}; .map(|c| hex_str_to_bytes(c).expect("invalid checkpoint"));
CliConfig { CliConfig {
checkpoint, checkpoint,
@ -121,21 +120,6 @@ impl Cli {
} }
} }
fn get_cached_checkpoint(&self) -> Option<Vec<u8>> {
let data_dir = self.get_data_dir();
let checkpoint_file = data_dir.join("checkpoint");
if checkpoint_file.exists() {
let checkpoint_res = fs::read(checkpoint_file);
match checkpoint_res {
Ok(checkpoint) => Some(checkpoint),
Err(_) => None,
}
} else {
None
}
}
fn get_data_dir(&self) -> PathBuf { fn get_data_dir(&self) -> PathBuf {
if let Some(dir) = &self.data_dir { if let Some(dir) = &self.data_dir {
PathBuf::from_str(dir).expect("cannot find data dir") PathBuf::from_str(dir).expect("cannot find data dir")

View File

@ -1,16 +1,14 @@
[package] [package]
name = "client" name = "client"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1", features = ["full"] }
eyre = "0.6.8" eyre = "0.6.8"
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
hex = "0.4.3" hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" } ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "d09f55b4f8554491e3431e01af1c32347a8781cd" }
ethers = "1.0.2" ethers = "1.0.0"
jsonrpsee = { version = "0.15.1", features = ["full"] }
futures = "0.3.23" futures = "0.3.23"
log = "0.4.17" log = "0.4.17"
thiserror = "1.0.37" thiserror = "1.0.37"
@ -19,3 +17,13 @@ common = { path = "../common" }
consensus = { path = "../consensus" } consensus = { path = "../consensus" }
execution = { path = "../execution" } execution = { path = "../execution" }
config = { path = "../config" } config = { path = "../config" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
jsonrpsee = { version = "0.15.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
gloo-timers = "0.2.6"
wasm-bindgen-futures = "0.4.33"
tokio = { version = "1", features = ["sync"] }

View File

@ -1,4 +1,3 @@
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use config::networks::Network; use config::networks::Network;
@ -12,13 +11,25 @@ use config::{CheckpointFallback, Config};
use consensus::{types::Header, ConsensusClient}; use consensus::{types::Header, ConsensusClient};
use execution::types::{CallOpts, ExecutionBlock}; use execution::types::{CallOpts, ExecutionBlock};
use log::{error, info, warn}; use log::{error, info, warn};
use tokio::spawn;
use tokio::sync::RwLock; use tokio::sync::RwLock;
#[cfg(not(target_arch = "wasm32"))]
use std::path::PathBuf;
#[cfg(not(target_arch = "wasm32"))]
use tokio::spawn;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep; use tokio::time::sleep;
use crate::database::{Database, FileDB}; #[cfg(target_arch = "wasm32")]
use gloo_timers::callback::Interval;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::spawn_local;
use crate::database::Database;
use crate::errors::NodeError; use crate::errors::NodeError;
use crate::node::Node; use crate::node::Node;
#[cfg(not(target_arch = "wasm32"))]
use crate::rpc::Rpc; use crate::rpc::Rpc;
#[derive(Default)] #[derive(Default)]
@ -27,7 +38,9 @@ pub struct ClientBuilder {
consensus_rpc: Option<String>, consensus_rpc: Option<String>,
execution_rpc: Option<String>, execution_rpc: Option<String>,
checkpoint: Option<Vec<u8>>, checkpoint: Option<Vec<u8>>,
#[cfg(not(target_arch = "wasm32"))]
rpc_port: Option<u16>, rpc_port: Option<u16>,
#[cfg(not(target_arch = "wasm32"))]
data_dir: Option<PathBuf>, data_dir: Option<PathBuf>,
config: Option<Config>, config: Option<Config>,
fallback: Option<String>, fallback: Option<String>,
@ -62,11 +75,13 @@ impl ClientBuilder {
self self
} }
#[cfg(not(target_arch = "wasm32"))]
pub fn rpc_port(mut self, port: u16) -> Self { pub fn rpc_port(mut self, port: u16) -> Self {
self.rpc_port = Some(port); self.rpc_port = Some(port);
self self
} }
#[cfg(not(target_arch = "wasm32"))]
pub fn data_dir(mut self, data_dir: PathBuf) -> Self { pub fn data_dir(mut self, data_dir: PathBuf) -> Self {
self.data_dir = Some(data_dir); self.data_dir = Some(data_dir);
self self
@ -92,7 +107,7 @@ impl ClientBuilder {
self self
} }
pub fn build(self) -> Result<Client<FileDB>> { pub fn build<DB: Database>(self) -> Result<Client<DB>> {
let base_config = if let Some(network) = self.network { let base_config = if let Some(network) = self.network {
network.to_base_config() network.to_base_config()
} else { } else {
@ -127,6 +142,7 @@ impl ClientBuilder {
base_config.checkpoint base_config.checkpoint
}; };
#[cfg(not(target_arch = "wasm32"))]
let rpc_port = if self.rpc_port.is_some() { let rpc_port = if self.rpc_port.is_some() {
self.rpc_port self.rpc_port
} else if let Some(config) = &self.config { } else if let Some(config) = &self.config {
@ -135,6 +151,7 @@ impl ClientBuilder {
None None
}; };
#[cfg(not(target_arch = "wasm32"))]
let data_dir = if self.data_dir.is_some() { let data_dir = if self.data_dir.is_some() {
self.data_dir self.data_dir
} else if let Some(config) = &self.config { } else if let Some(config) = &self.config {
@ -167,8 +184,14 @@ impl ClientBuilder {
consensus_rpc, consensus_rpc,
execution_rpc, execution_rpc,
checkpoint, checkpoint,
#[cfg(not(target_arch = "wasm32"))]
rpc_port, rpc_port,
#[cfg(target_arch = "wasm32")]
rpc_port: None,
#[cfg(not(target_arch = "wasm32"))]
data_dir, data_dir,
#[cfg(target_arch = "wasm32")]
data_dir: None,
chain: base_config.chain, chain: base_config.chain,
forks: base_config.forks, forks: base_config.forks,
max_checkpoint_age: base_config.max_checkpoint_age, max_checkpoint_age: base_config.max_checkpoint_age,
@ -183,35 +206,38 @@ impl ClientBuilder {
pub struct Client<DB: Database> { pub struct Client<DB: Database> {
node: Arc<RwLock<Node>>, node: Arc<RwLock<Node>>,
#[cfg(not(target_arch = "wasm32"))]
rpc: Option<Rpc>, rpc: Option<Rpc>,
db: Option<DB>, db: DB,
fallback: Option<String>, fallback: Option<String>,
load_external_fallback: bool, load_external_fallback: bool,
} }
impl Client<FileDB> { impl<DB: Database> Client<DB> {
fn new(config: Config) -> Result<Self> { fn new(mut config: Config) -> Result<Self> {
let db = DB::new(&config)?;
let checkpoint = db.load_checkpoint()?;
config.checkpoint = checkpoint;
let config = Arc::new(config); let config = Arc::new(config);
let node = Node::new(config.clone())?; let node = Node::new(config.clone())?;
let node = Arc::new(RwLock::new(node)); let node = Arc::new(RwLock::new(node));
#[cfg(not(target_arch = "wasm32"))]
let rpc = config.rpc_port.map(|port| Rpc::new(node.clone(), port)); let rpc = config.rpc_port.map(|port| Rpc::new(node.clone(), port));
let data_dir = config.data_dir.clone();
let db = data_dir.map(FileDB::new);
Ok(Client { Ok(Client {
node, node,
#[cfg(not(target_arch = "wasm32"))]
rpc, rpc,
db, db,
fallback: config.fallback.clone(), fallback: config.fallback.clone(),
load_external_fallback: config.load_external_fallback, load_external_fallback: config.load_external_fallback,
}) })
} }
}
impl<DB: Database> Client<DB> {
pub async fn start(&mut self) -> Result<()> { pub async fn start(&mut self) -> Result<()> {
#[cfg(not(target_arch = "wasm32"))]
if let Some(rpc) = &mut self.rpc { if let Some(rpc) = &mut self.rpc {
rpc.start().await?; rpc.start().await?;
} }
@ -241,6 +267,13 @@ impl<DB: Database> Client<DB> {
} }
} }
self.start_advance_thread();
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
fn start_advance_thread(&self) {
let node = self.node.clone(); let node = self.node.clone();
spawn(async move { spawn(async move {
loop { loop {
@ -250,11 +283,25 @@ impl<DB: Database> Client<DB> {
} }
let next_update = node.read().await.duration_until_next_update(); let next_update = node.read().await.duration_until_next_update();
sleep(next_update).await; sleep(next_update).await;
} }
}); });
}
Ok(()) #[cfg(target_arch = "wasm32")]
fn start_advance_thread(&self) {
let node = self.node.clone();
Interval::new(12000, move || {
let node = node.clone();
spawn_local(async move {
let res = node.write().await.advance().await;
if let Err(err) = res {
warn!("consensus error: {}", err);
}
});
})
.forget();
} }
async fn boot_from_fallback(&self) -> eyre::Result<()> { async fn boot_from_fallback(&self) -> eyre::Result<()> {
@ -335,8 +382,8 @@ impl<DB: Database> Client<DB> {
}; };
info!("saving last checkpoint hash"); info!("saving last checkpoint hash");
let res = self.db.as_ref().map(|db| db.save_checkpoint(checkpoint)); let res = self.db.save_checkpoint(checkpoint);
if res.is_some() && res.unwrap().is_err() { if res.is_err() {
warn!("checkpoint save failed"); warn!("checkpoint save failed");
} }
} }

View File

@ -1,27 +1,40 @@
#[cfg(not(target_arch = "wasm32"))]
use std::{ use std::{
fs, fs,
io::{Read, Write}, io::{Read, Write},
path::PathBuf, path::PathBuf,
}; };
use config::Config;
use eyre::Result; use eyre::Result;
pub trait Database { pub trait Database {
fn new(config: &Config) -> Result<Self>
where
Self: Sized;
fn save_checkpoint(&self, checkpoint: Vec<u8>) -> Result<()>; fn save_checkpoint(&self, checkpoint: Vec<u8>) -> Result<()>;
fn load_checkpoint(&self) -> Result<Vec<u8>>; fn load_checkpoint(&self) -> Result<Vec<u8>>;
} }
#[cfg(not(target_arch = "wasm32"))]
pub struct FileDB { pub struct FileDB {
data_dir: PathBuf, data_dir: PathBuf,
default_checkpoint: Vec<u8>,
} }
impl FileDB { #[cfg(not(target_arch = "wasm32"))]
pub fn new(data_dir: PathBuf) -> Self {
FileDB { data_dir }
}
}
impl Database for FileDB { impl Database for FileDB {
fn new(config: &Config) -> Result<Self> {
if let Some(data_dir) = &config.data_dir {
return Ok(FileDB {
data_dir: data_dir.to_path_buf(),
default_checkpoint: config.checkpoint.clone(),
});
}
eyre::bail!("data dir not in config")
}
fn save_checkpoint(&self, checkpoint: Vec<u8>) -> Result<()> { fn save_checkpoint(&self, checkpoint: Vec<u8>) -> Result<()> {
fs::create_dir_all(&self.data_dir)?; fs::create_dir_all(&self.data_dir)?;
@ -44,6 +57,30 @@ impl Database for FileDB {
let mut buf = Vec::new(); let mut buf = Vec::new();
f.read_to_end(&mut buf)?; f.read_to_end(&mut buf)?;
Ok(buf) if buf.len() == 32 {
Ok(buf)
} else {
Ok(self.default_checkpoint.clone())
}
}
}
pub struct ConfigDB {
checkpoint: Vec<u8>,
}
impl Database for ConfigDB {
fn new(config: &Config) -> Result<Self> {
Ok(Self {
checkpoint: config.checkpoint.clone(),
})
}
fn load_checkpoint(&self) -> Result<Vec<u8>> {
Ok(self.checkpoint.clone())
}
fn save_checkpoint(&self, _checkpoint: Vec<u8>) -> Result<()> {
Ok(())
} }
} }

View File

@ -37,6 +37,7 @@ pub enum NodeError {
BlockNotFoundError(#[from] BlockNotFoundError), BlockNotFoundError(#[from] BlockNotFoundError),
} }
#[cfg(not(target_arch = "wasm32"))]
impl NodeError { impl NodeError {
pub fn to_json_rpsee_error(self) -> jsonrpsee::core::Error { pub fn to_json_rpsee_error(self) -> jsonrpsee::core::Error {
match self { match self {

View File

@ -3,6 +3,8 @@ pub use crate::client::*;
pub mod database; pub mod database;
pub mod errors; pub mod errors;
#[cfg(not(target_arch = "wasm32"))]
pub mod rpc; pub mod rpc;
mod node; pub mod node;

View File

@ -1,12 +1,12 @@
[package] [package]
name = "common" name = "common"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
eyre = "0.6.8" eyre = "0.6.8"
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
hex = "0.4.3" hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" } ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "d09f55b4f8554491e3431e01af1c32347a8781cd" }
ethers = "1.0.2" ethers = "1.0.0"
thiserror = "1.0.37" thiserror = "1.0.37"

View File

@ -1,24 +1,24 @@
[package] [package]
name = "config" name = "config"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1", features = ["full"] }
eyre = "0.6.8" eyre = "0.6.8"
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
hex = "0.4.3" hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" } ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "d09f55b4f8554491e3431e01af1c32347a8781cd" }
ethers = "1.0.2" ethers = "1.0.0"
figment = { version = "0.10.7", features = ["toml", "env"] } figment = { version = "0.10.7", features = ["toml", "env"] }
thiserror = "1.0.37" thiserror = "1.0.37"
log = "0.4.17" log = "0.4.17"
common = { path = "../common" }
reqwest = "0.11.13" reqwest = "0.11.13"
serde_yaml = "0.9.14" serde_yaml = "0.9.14"
strum = "0.24.1" strum = "0.24.1"
futures = "0.3.25" futures = "0.3.25"
common = { path = "../common" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["full"] }

View File

@ -136,26 +136,25 @@ impl CheckpointFallback {
// Iterate over all mainnet checkpoint sync services and get the latest checkpoint slot for each. // Iterate over all mainnet checkpoint sync services and get the latest checkpoint slot for each.
let tasks: Vec<_> = services let tasks: Vec<_> = services
.iter() .iter()
.map(|service| { .map(|service| async move {
let service = service.clone(); let service = service.clone();
tokio::spawn(async move { match Self::query_service(&service.endpoint).await {
match Self::query_service(&service.endpoint).await { Some(raw) => {
Some(raw) => { if raw.data.slots.is_empty() {
if raw.data.slots.is_empty() { return Err(eyre::eyre!("no slots"));
return Err(eyre::eyre!("no slots"));
}
Ok(raw.data.slots[0].clone())
} }
None => Err(eyre::eyre!("failed to query service")), Ok(raw.data.slots[0].clone())
} }
}) None => Err(eyre::eyre!("failed to query service")),
}
}) })
.collect(); .collect();
let slots = futures::future::join_all(tasks) let slots = futures::future::join_all(tasks)
.await .await
.iter() .iter()
.filter_map(|slot| match &slot { .filter_map(|slot| match &slot {
Ok(Ok(s)) => Some(s.clone()), Ok(s) => Some(s.clone()),
_ => None, _ => None,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -36,7 +36,7 @@ impl Network {
pub fn mainnet() -> BaseConfig { pub fn mainnet() -> BaseConfig {
BaseConfig { BaseConfig {
checkpoint: hex_str_to_bytes( checkpoint: hex_str_to_bytes(
"0x428ce0b5f5bbed1fc2b3feb5d4152ae0fe98a80b1bfa8de36681868e81e9222a", "0x766647f3c4e1fc91c0db9a9374032ae038778411fbff222974e11f2e3ce7dadf",
) )
.unwrap(), .unwrap(),
rpc_port: 8545, rpc_port: 8545,

View File

@ -1,27 +1,31 @@
[package] [package]
name = "consensus" name = "consensus"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1", features = ["full"] }
eyre = "0.6.8" eyre = "0.6.8"
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"
hex = "0.4.3" hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" } ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "d09f55b4f8554491e3431e01af1c32347a8781cd" }
blst = "0.3.10" milagro_bls = { git = "https://github.com/Snowfork/milagro_bls" }
ethers = "1.0.2" ethers = "1.0.0"
bytes = "1.2.1" bytes = "1.2.1"
toml = "0.5.9" toml = "0.5.9"
async-trait = "0.1.57" async-trait = "0.1.57"
log = "0.4.17" log = "0.4.17"
chrono = "0.4.22" chrono = "0.4.22"
thiserror = "1.0.37" thiserror = "1.0.37"
openssl = { version = "0.10", features = ["vendored"] } reqwest = { version = "0.11.13", features = ["json"] }
reqwest = { version = "0.11.12", features = ["json"] }
reqwest-middleware = "0.1.6"
reqwest-retry = "0.1.5"
common = { path = "../common" } common = { path = "../common" }
config = { path = "../config" } config = { path = "../config" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
tokio = { version = "1", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-timer = "0.2.5"

View File

@ -1,13 +1,12 @@
use std::cmp; use std::cmp;
use std::sync::Arc; use std::sync::Arc;
use std::time::UNIX_EPOCH;
use blst::min_pk::PublicKey;
use chrono::Duration; use chrono::Duration;
use eyre::eyre; use eyre::eyre;
use eyre::Result; use eyre::Result;
use log::warn; use log::warn;
use log::{debug, info}; use log::{debug, info};
use milagro_bls::PublicKey;
use ssz_rs::prelude::*; use ssz_rs::prelude::*;
use common::types::*; use common::types::*;
@ -21,9 +20,20 @@ use super::rpc::ConsensusRpc;
use super::types::*; use super::types::*;
use super::utils::*; use super::utils::*;
#[cfg(not(target_arch = "wasm32"))]
use std::time::SystemTime;
#[cfg(not(target_arch = "wasm32"))]
use std::time::UNIX_EPOCH;
#[cfg(target_arch = "wasm32")]
use wasm_timer::SystemTime;
#[cfg(target_arch = "wasm32")]
use wasm_timer::UNIX_EPOCH;
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md
// does not implement force updates // does not implement force updates
#[derive(Debug)]
pub struct ConsensusClient<R: ConsensusRpc> { pub struct ConsensusClient<R: ConsensusRpc> {
rpc: R, rpc: R,
store: LightClientStore, store: LightClientStore,
@ -480,18 +490,13 @@ impl<R: ConsensusRpc> ConsensusClient<R> {
fn age(&self, slot: u64) -> Duration { fn age(&self, slot: u64) -> Duration {
let expected_time = self.slot_timestamp(slot); let expected_time = self.slot_timestamp(slot);
let now = std::time::SystemTime::now() let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
.duration_since(UNIX_EPOCH)
.unwrap();
let delay = now - std::time::Duration::from_secs(expected_time); let delay = now - std::time::Duration::from_secs(expected_time);
chrono::Duration::from_std(delay).unwrap() chrono::Duration::from_std(delay).unwrap()
} }
pub fn expected_current_slot(&self) -> u64 { pub fn expected_current_slot(&self) -> u64 {
let now = std::time::SystemTime::now() let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
.duration_since(UNIX_EPOCH)
.unwrap();
let genesis_time = self.config.chain.genesis_time; let genesis_time = self.config.chain.genesis_time;
let since_genesis = now - std::time::Duration::from_secs(genesis_time); let since_genesis = now - std::time::Duration::from_secs(genesis_time);
@ -509,7 +514,7 @@ impl<R: ConsensusRpc> ConsensusClient<R> {
let next_slot = current_slot + 1; let next_slot = current_slot + 1;
let next_slot_timestamp = self.slot_timestamp(next_slot); let next_slot_timestamp = self.slot_timestamp(next_slot);
let now = std::time::SystemTime::now() let now = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs(); .as_secs();
@ -540,7 +545,7 @@ fn get_participating_keys(
bitfield.iter().enumerate().for_each(|(i, bit)| { bitfield.iter().enumerate().for_each(|(i, bit)| {
if bit == true { if bit == true {
let pk = &committee.pubkeys[i]; let pk = &committee.pubkeys[i];
let pk = PublicKey::from_bytes(pk).unwrap(); let pk = PublicKey::from_bytes_unchecked(pk).unwrap();
pks.push(pk); pks.push(pk);
} }
}); });

View File

@ -8,7 +8,8 @@ pub struct MockRpc {
testdata: PathBuf, testdata: PathBuf,
} }
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl ConsensusRpc for MockRpc { impl ConsensusRpc for MockRpc {
fn new(path: &str) -> Self { fn new(path: &str) -> Self {
MockRpc { MockRpc {

View File

@ -7,7 +7,8 @@ use eyre::Result;
use crate::types::{BeaconBlock, Bootstrap, FinalityUpdate, OptimisticUpdate, Update}; use crate::types::{BeaconBlock, Bootstrap, FinalityUpdate, OptimisticUpdate, Update};
// implements https://github.com/ethereum/beacon-APIs/tree/master/apis/beacon/light_client // implements https://github.com/ethereum/beacon-APIs/tree/master/apis/beacon/light_client
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait ConsensusRpc { pub trait ConsensusRpc {
fn new(path: &str) -> Self; fn new(path: &str) -> Self;
async fn get_bootstrap(&self, block_root: &'_ [u8]) -> Result<Bootstrap>; async fn get_bootstrap(&self, block_root: &'_ [u8]) -> Result<Bootstrap>;

View File

@ -1,7 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use eyre::Result; use eyre::Result;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
use std::cmp; use std::cmp;
use super::ConsensusRpc; use super::ConsensusRpc;
@ -9,25 +7,17 @@ use crate::constants::MAX_REQUEST_LIGHT_CLIENT_UPDATES;
use crate::types::*; use crate::types::*;
use common::errors::RpcError; use common::errors::RpcError;
#[derive(Debug)]
pub struct NimbusRpc { pub struct NimbusRpc {
rpc: String, rpc: String,
client: ClientWithMiddleware,
} }
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl ConsensusRpc for NimbusRpc { impl ConsensusRpc for NimbusRpc {
fn new(rpc: &str) -> Self { fn new(rpc: &str) -> Self {
let retry_policy = ExponentialBackoff::builder()
.backoff_exponent(1)
.build_with_max_retries(3);
let client = ClientBuilder::new(reqwest::Client::new())
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build();
NimbusRpc { NimbusRpc {
rpc: rpc.to_string(), rpc: rpc.to_string(),
client,
} }
} }
@ -38,8 +28,8 @@ impl ConsensusRpc for NimbusRpc {
self.rpc, root_hex self.rpc, root_hex
); );
let res = self let client = reqwest::Client::new();
.client let res = client
.get(req) .get(req)
.send() .send()
.await .await
@ -58,8 +48,8 @@ impl ConsensusRpc for NimbusRpc {
self.rpc, period, count self.rpc, period, count
); );
let res = self let client = reqwest::Client::new();
.client let res = client
.get(req) .get(req)
.send() .send()
.await .await
@ -73,10 +63,7 @@ impl ConsensusRpc for NimbusRpc {
async fn get_finality_update(&self) -> Result<FinalityUpdate> { async fn get_finality_update(&self) -> Result<FinalityUpdate> {
let req = format!("{}/eth/v1/beacon/light_client/finality_update", self.rpc); let req = format!("{}/eth/v1/beacon/light_client/finality_update", self.rpc);
let res = self let res = reqwest::get(req)
.client
.get(req)
.send()
.await .await
.map_err(|e| RpcError::new("finality_update", e))? .map_err(|e| RpcError::new("finality_update", e))?
.json::<FinalityUpdateResponse>() .json::<FinalityUpdateResponse>()
@ -88,10 +75,7 @@ impl ConsensusRpc for NimbusRpc {
async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> { async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> {
let req = format!("{}/eth/v1/beacon/light_client/optimistic_update", self.rpc); let req = format!("{}/eth/v1/beacon/light_client/optimistic_update", self.rpc);
let res = self let res = reqwest::get(req)
.client
.get(req)
.send()
.await .await
.map_err(|e| RpcError::new("optimistic_update", e))? .map_err(|e| RpcError::new("optimistic_update", e))?
.json::<OptimisticUpdateResponse>() .json::<OptimisticUpdateResponse>()
@ -103,10 +87,7 @@ impl ConsensusRpc for NimbusRpc {
async fn get_block(&self, slot: u64) -> Result<BeaconBlock> { async fn get_block(&self, slot: u64) -> Result<BeaconBlock> {
let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot); let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot);
let res = self let res = reqwest::get(req)
.client
.get(req)
.send()
.await .await
.map_err(|e| RpcError::new("blocks", e))? .map_err(|e| RpcError::new("blocks", e))?
.json::<BeaconBlockResponse>() .json::<BeaconBlockResponse>()
@ -118,10 +99,7 @@ impl ConsensusRpc for NimbusRpc {
async fn chain_id(&self) -> Result<u64> { async fn chain_id(&self) -> Result<u64> {
let req = format!("{}/eth/v1/config/spec", self.rpc); let req = format!("{}/eth/v1/config/spec", self.rpc);
let res = self let res = reqwest::get(req)
.client
.get(req)
.send()
.await .await
.map_err(|e| RpcError::new("spec", e))? .map_err(|e| RpcError::new("spec", e))?
.json::<SpecResponse>() .json::<SpecResponse>()

View File

@ -1,9 +1,6 @@
use blst::{
min_pk::{PublicKey, Signature},
BLST_ERROR,
};
use common::{types::Bytes32, utils::bytes32_to_node}; use common::{types::Bytes32, utils::bytes32_to_node};
use eyre::Result; use eyre::Result;
use milagro_bls::{AggregateSignature, PublicKey};
use ssz_rs::prelude::*; use ssz_rs::prelude::*;
use crate::types::{Header, SignatureBytes}; use crate::types::{Header, SignatureBytes};
@ -14,10 +11,9 @@ pub fn calc_sync_period(slot: u64) -> u64 {
} }
pub fn is_aggregate_valid(sig_bytes: &SignatureBytes, msg: &[u8], pks: &[&PublicKey]) -> bool { pub fn is_aggregate_valid(sig_bytes: &SignatureBytes, msg: &[u8], pks: &[&PublicKey]) -> bool {
let dst: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; let sig_res = AggregateSignature::from_bytes(sig_bytes);
let sig_res = Signature::from_bytes(sig_bytes);
match sig_res { match sig_res {
Ok(sig) => sig.fast_aggregate_verify(true, msg, dst, pks) == BLST_ERROR::BLST_SUCCESS, Ok(sig) => sig.fast_aggregate_verify(msg, pks),
Err(_) => false, Err(_) => false,
} }
} }

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use env_logger::Env; use env_logger::Env;
use ethers::{types::Address, utils}; use ethers::{types::Address, utils};
use eyre::Result; use eyre::Result;
use helios::{client::ClientBuilder, config::networks::Network, types::BlockTag}; use helios::{config::networks::Network, prelude::*};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -15,12 +15,13 @@ async fn main() -> Result<()> {
let consensus_rpc = "https://www.lightclientdata.org"; let consensus_rpc = "https://www.lightclientdata.org";
log::info!("Using consensus RPC URL: {}", consensus_rpc); log::info!("Using consensus RPC URL: {}", consensus_rpc);
let mut client = ClientBuilder::new() let mut client: Client<FileDB> = ClientBuilder::new()
.network(Network::MAINNET) .network(Network::MAINNET)
.consensus_rpc(consensus_rpc) .consensus_rpc(consensus_rpc)
.execution_rpc(untrusted_rpc_url) .execution_rpc(untrusted_rpc_url)
.load_external_fallback() .load_external_fallback()
.build()?; .build()?;
log::info!( log::info!(
"Built client on network \"{}\" with external checkpoint fallbacks", "Built client on network \"{}\" with external checkpoint fallbacks",
Network::MAINNET Network::MAINNET

View File

@ -35,7 +35,7 @@ async fn main() -> Result<()> {
builder = builder.load_external_fallback(); builder = builder.load_external_fallback();
// Build the client // Build the client
let _client = builder.build().unwrap(); let _client: Client<FileDB> = builder.build().unwrap();
println!("Constructed client!"); println!("Constructed client!");
Ok(()) Ok(())

View File

@ -1,18 +1,17 @@
[package] [package]
name = "execution" name = "execution"
version = "0.1.3" version = "0.2.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6.8" eyre = "0.6.8"
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"
hex = "0.4.3" hex = "0.4.3"
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "cb08f18ca919cc1b685b861d0fa9e2daabe89737" } ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs", rev = "d09f55b4f8554491e3431e01af1c32347a8781cd" }
ethers = "1.0.2" revm = { version = "2.3", default-features = false, features = ["std", "k256", "with-serde"] }
revm = "2.1.0" ethers = "1.0.0"
bytes = "1.2.1" bytes = "1.2.1"
futures = "0.3.23" futures = "0.3.23"
toml = "0.5.9" toml = "0.5.9"
@ -20,7 +19,10 @@ triehash-ethereum = { git = "https://github.com/openethereum/parity-ethereum", r
async-trait = "0.1.57" async-trait = "0.1.57"
log = "0.4.17" log = "0.4.17"
thiserror = "1.0.37" thiserror = "1.0.37"
openssl = { version = "0.10", features = ["vendored"] }
common = { path = "../common" } common = { path = "../common" }
consensus = { path = "../consensus" } consensus = { path = "../consensus" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
openssl = { version = "0.10", features = ["vendored"] }
tokio = { version = "1", features = ["full"] }

View File

@ -9,14 +9,13 @@ use bytes::Bytes;
use common::{errors::BlockNotFoundError, types::BlockTag}; use common::{errors::BlockNotFoundError, types::BlockTag};
use ethers::{ use ethers::{
abi::ethereum_types::BigEndianHash, abi::ethereum_types::BigEndianHash,
prelude::{Address, H160, H256, U256},
types::transaction::eip2930::AccessListItem, types::transaction::eip2930::AccessListItem,
types::{Address, H160, H256, U256},
}; };
use eyre::{Report, Result}; use eyre::{Report, Result};
use futures::future::join_all; use futures::{executor::block_on, future::join_all};
use log::trace; use log::trace;
use revm::{AccountInfo, Bytecode, Database, Env, TransactOut, TransactTo, EVM}; use revm::{AccountInfo, Bytecode, Database, Env, TransactOut, TransactTo, EVM};
use tokio::runtime::Runtime;
use consensus::types::ExecutionPayload; use consensus::types::ExecutionPayload;
@ -225,8 +224,9 @@ impl<'a, R: ExecutionRpc> ProofDB<'a, R> {
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
let account_fut = execution.get_account(&address, Some(&slots), &payload); let account_fut = execution.get_account(&address, Some(&slots), &payload);
let runtime = Runtime::new()?; // let runtime = Runtime::new()?;
runtime.block_on(account_fut) // runtime.block_on(account_fut)
block_on(account_fut)
}); });
handle.join().unwrap() handle.join().unwrap()

View File

@ -27,13 +27,16 @@ impl Clone for HttpRpc {
} }
} }
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl ExecutionRpc for HttpRpc { impl ExecutionRpc for HttpRpc {
fn new(rpc: &str) -> Result<Self> { fn new(rpc: &str) -> Result<Self> {
let http = Http::from_str(rpc)?; let http = Http::from_str(rpc)?;
let mut client = RetryClient::new(http, Box::new(HttpRateLimitRetryPolicy), 100, 50); let mut client = RetryClient::new(http, Box::new(HttpRateLimitRetryPolicy), 100, 50);
client.set_compute_units(300); client.set_compute_units(300);
let provider = Provider::new(client); let provider = Provider::new(client);
Ok(HttpRpc { Ok(HttpRpc {
url: rpc.to_string(), url: rpc.to_string(),
provider, provider,

View File

@ -17,7 +17,8 @@ pub struct MockRpc {
path: PathBuf, path: PathBuf,
} }
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl ExecutionRpc for MockRpc { impl ExecutionRpc for MockRpc {
fn new(rpc: &str) -> Result<Self> { fn new(rpc: &str) -> Result<Self> {
let path = PathBuf::from(rpc); let path = PathBuf::from(rpc);

View File

@ -10,7 +10,8 @@ use crate::types::CallOpts;
pub mod http_rpc; pub mod http_rpc;
pub mod mock_rpc; pub mod mock_rpc;
#[async_trait] #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait ExecutionRpc: Send + Clone + Sync + 'static { pub trait ExecutionRpc: Send + Clone + Sync + 'static {
fn new(rpc: &str) -> Result<Self> fn new(rpc: &str) -> Result<Self>
where where