refactor: better error handling (#63)

* add custom errors to consensus

* add BlockNotFoundError

* better handling of blocktag parsing

* clean up

* add execution errors

* add rpc errors

* add more fields to errors
This commit is contained in:
Noah Citron 2022-09-29 19:35:43 -04:00 committed by GitHub
parent f3b9750eff
commit 5d1f4a6344
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 295 additions and 102 deletions

11
Cargo.lock generated
View File

@ -513,6 +513,7 @@ dependencies = [
"openssl", "openssl",
"serde", "serde",
"ssz-rs", "ssz-rs",
"thiserror",
"toml", "toml",
] ]
@ -551,6 +552,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"ssz-rs", "ssz-rs",
"thiserror",
"tokio", "tokio",
"toml", "toml",
] ]
@ -1040,6 +1042,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"ssz-rs", "ssz-rs",
"thiserror",
"tokio", "tokio",
"toml", "toml",
"triehash-ethereum", "triehash-ethereum",
@ -3285,18 +3288,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.34" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.34" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -4,6 +4,7 @@ use ethers::prelude::{Address, U256};
use ethers::types::{Transaction, TransactionReceipt, H256}; use ethers::types::{Transaction, TransactionReceipt, H256};
use eyre::{eyre, Result}; use eyre::{eyre, Result};
use common::types::BlockTag;
use config::Config; use config::Config;
use consensus::types::Header; use consensus::types::Header;
use execution::types::{CallOpts, ExecutionBlock}; use execution::types::{CallOpts, ExecutionBlock};
@ -13,7 +14,7 @@ use tokio::sync::RwLock;
use tokio::time::sleep; use tokio::time::sleep;
use crate::database::{Database, FileDB}; use crate::database::{Database, FileDB};
use crate::node::{BlockTag, Node}; use crate::node::Node;
use crate::rpc::Rpc; use crate::rpc::Rpc;
pub struct Client<DB: Database> { pub struct Client<DB: Database> {
@ -49,13 +50,13 @@ impl<DB: Database> Client<DB> {
spawn(async move { spawn(async move {
let res = node.write().await.sync().await; let res = node.write().await.sync().await;
if let Err(err) = res { if let Err(err) = res {
warn!("{}", err); warn!("consensus error: {}", err);
} }
loop { loop {
let res = node.write().await.advance().await; let res = node.write().await.advance().await;
if let Err(err) = res { if let Err(err) = res {
warn!("{}", err); warn!("consensus error: {}", err);
} }
let next_update = node.read().await.duration_until_next_update(); let next_update = node.read().await.duration_until_next_update();

View File

@ -6,6 +6,8 @@ use ethers::prelude::{Address, U256};
use ethers::types::{Transaction, TransactionReceipt, H256}; use ethers::types::{Transaction, TransactionReceipt, H256};
use eyre::{eyre, Result}; use eyre::{eyre, Result};
use common::errors::BlockNotFoundError;
use common::types::BlockTag;
use config::Config; use config::Config;
use consensus::rpc::nimbus_rpc::NimbusRpc; use consensus::rpc::nimbus_rpc::NimbusRpc;
use consensus::types::{ExecutionPayload, Header}; use consensus::types::{ExecutionPayload, Header};
@ -147,7 +149,7 @@ impl Node {
let value = account.slots.get(&slot); let value = account.slots.get(&slot);
match value { match value {
Some(value) => Ok(*value), Some(value) => Ok(*value),
None => Err(eyre!("Slot Not Found")), None => Err(eyre!("slot not found")),
} }
} }
@ -230,15 +232,17 @@ impl Node {
match block { match block {
BlockTag::Latest => { BlockTag::Latest => {
let payload = self.payloads.last_key_value(); let payload = self.payloads.last_key_value();
Ok(payload.ok_or(eyre!("Block Not Found"))?.1) Ok(payload.ok_or(BlockNotFoundError::new(BlockTag::Latest))?.1)
} }
BlockTag::Finalized => { BlockTag::Finalized => {
let payload = self.finalized_payloads.last_key_value(); let payload = self.finalized_payloads.last_key_value();
Ok(payload.ok_or(eyre!("Block Not Found"))?.1) Ok(payload
.ok_or(BlockNotFoundError::new(BlockTag::Finalized))?
.1)
} }
BlockTag::Number(num) => { BlockTag::Number(num) => {
let payload = self.payloads.get(num); let payload = self.payloads.get(num);
payload.ok_or(eyre!("Block Not Found")) payload.ok_or(BlockNotFoundError::new(BlockTag::Number(*num)).into())
} }
} }
} }
@ -249,7 +253,7 @@ impl Node {
let slot_delay = expected_slot - synced_slot; let slot_delay = expected_slot - synced_slot;
if slot_delay > 10 { if slot_delay > 10 {
return Err(eyre!("Out of Sync")); return Err(eyre!("out of sync"));
} }
Ok(()) Ok(())
@ -263,9 +267,3 @@ impl Node {
} }
} }
} }
pub enum BlockTag {
Latest,
Finalized,
Number(u64),
}

View File

@ -13,9 +13,12 @@ use jsonrpsee::{
proc_macros::rpc, proc_macros::rpc,
}; };
use crate::node::{BlockTag, Node}; use crate::node::Node;
use common::utils::{hex_str_to_bytes, u64_to_hex_string}; use common::{
types::BlockTag,
utils::{hex_str_to_bytes, u64_to_hex_string},
};
use execution::types::{CallOpts, ExecutionBlock}; use execution::types::{CallOpts, ExecutionBlock};
pub struct Rpc { pub struct Rpc {
@ -47,16 +50,16 @@ impl Rpc {
} }
} }
#[rpc(client, server, namespace = "eth")] #[rpc(server, namespace = "eth")]
trait EthRpc { trait EthRpc {
#[method(name = "getBalance")] #[method(name = "getBalance")]
async fn get_balance(&self, address: &str, block: &str) -> Result<String, Error>; async fn get_balance(&self, address: &str, block: BlockTag) -> Result<String, Error>;
#[method(name = "getTransactionCount")] #[method(name = "getTransactionCount")]
async fn get_transaction_count(&self, address: &str, block: &str) -> Result<String, Error>; async fn get_transaction_count(&self, address: &str, block: BlockTag) -> Result<String, Error>;
#[method(name = "getCode")] #[method(name = "getCode")]
async fn get_code(&self, address: &str, block: &str) -> Result<String, Error>; async fn get_code(&self, address: &str, block: BlockTag) -> Result<String, Error>;
#[method(name = "call")] #[method(name = "call")]
async fn call(&self, opts: CallOpts, block: &str) -> Result<String, Error>; async fn call(&self, opts: CallOpts, block: BlockTag) -> Result<String, Error>;
#[method(name = "estimateGas")] #[method(name = "estimateGas")]
async fn estimate_gas(&self, opts: CallOpts) -> Result<String, Error>; async fn estimate_gas(&self, opts: CallOpts) -> Result<String, Error>;
#[method(name = "chainId")] #[method(name = "chainId")]
@ -70,7 +73,7 @@ trait EthRpc {
#[method(name = "getBlockByNumber")] #[method(name = "getBlockByNumber")]
async fn get_block_by_number( async fn get_block_by_number(
&self, &self,
num: &str, block: BlockTag,
full_tx: bool, full_tx: bool,
) -> Result<Option<ExecutionBlock>, Error>; ) -> Result<Option<ExecutionBlock>, Error>;
#[method(name = "getBlockByHash")] #[method(name = "getBlockByHash")]
@ -104,9 +107,8 @@ struct RpcInner {
#[async_trait] #[async_trait]
impl EthRpcServer for RpcInner { impl EthRpcServer for RpcInner {
async fn get_balance(&self, address: &str, block: &str) -> Result<String, Error> { async fn get_balance(&self, address: &str, block: BlockTag) -> Result<String, Error> {
debug!("eth_getBalance"); debug!("eth_getBalance");
let block = convert_err(decode_block(block))?;
let address = convert_err(Address::from_str(address))?; let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await; 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)?;
@ -114,8 +116,7 @@ impl EthRpcServer for RpcInner {
Ok(balance.encode_hex()) Ok(balance.encode_hex())
} }
async fn get_transaction_count(&self, address: &str, block: &str) -> Result<String, Error> { async fn get_transaction_count(&self, address: &str, block: BlockTag) -> Result<String, Error> {
let block = convert_err(decode_block(block))?;
let address = convert_err(Address::from_str(address))?; let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await; 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)?;
@ -123,8 +124,7 @@ impl EthRpcServer for RpcInner {
Ok(nonce.encode_hex()) Ok(nonce.encode_hex())
} }
async fn get_code(&self, address: &str, block: &str) -> Result<String, Error> { async fn get_code(&self, address: &str, block: BlockTag) -> Result<String, Error> {
let block = convert_err(decode_block(block))?;
let address = convert_err(Address::from_str(address))?; let address = convert_err(Address::from_str(address))?;
let node = self.node.read().await; 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)?;
@ -132,9 +132,8 @@ impl EthRpcServer for RpcInner {
Ok(hex::encode(code)) Ok(hex::encode(code))
} }
async fn call(&self, opts: CallOpts, block: &str) -> Result<String, Error> { async fn call(&self, opts: CallOpts, block: BlockTag) -> Result<String, Error> {
debug!("eth_call"); debug!("eth_call");
let block = convert_err(decode_block(block))?;
let node = self.node.read().await; let node = self.node.read().await;
let res = convert_err(node.call(&opts, &block))?; let res = convert_err(node.call(&opts, &block))?;
@ -175,10 +174,9 @@ impl EthRpcServer for RpcInner {
async fn get_block_by_number( async fn get_block_by_number(
&self, &self,
block: &str, block: BlockTag,
_full_tx: bool, _full_tx: bool,
) -> Result<Option<ExecutionBlock>, Error> { ) -> Result<Option<ExecutionBlock>, Error> {
let block = convert_err(decode_block(block))?;
let node = self.node.read().await; let node = self.node.read().await;
let block = convert_err(node.get_block_by_number(&block))?; let block = convert_err(node.get_block_by_number(&block))?;
Ok(block) Ok(block)
@ -251,20 +249,3 @@ fn convert_err<T, E: Display>(res: Result<T, E>) -> Result<T, Error> {
Error::Custom(err.to_string()) Error::Custom(err.to_string())
}) })
} }
fn decode_block(block: &str) -> Result<BlockTag> {
match block {
"latest" => Ok(BlockTag::Latest),
"finalized" => Ok(BlockTag::Finalized),
_ => {
if block.starts_with("0x") {
Ok(BlockTag::Number(u64::from_str_radix(
block.strip_prefix("0x").unwrap(),
16,
)?))
} else {
Ok(BlockTag::Number(block.parse()?))
}
}
}
}

View File

@ -13,3 +13,4 @@ ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs" }
ethers = "0.17.0" ethers = "0.17.0"
toml = "0.5.9" toml = "0.5.9"
openssl = { version = "0.10", features = ["vendored"] } openssl = { version = "0.10", features = ["vendored"] }
thiserror = "1.0.37"

27
common/src/errors.rs Normal file
View File

@ -0,0 +1,27 @@
use thiserror::Error;
use crate::types::BlockTag;
#[derive(Debug, Error)]
#[error("block not available: {block}")]
pub struct BlockNotFoundError {
block: BlockTag,
}
impl BlockNotFoundError {
pub fn new(block: BlockTag) -> Self {
Self { block }
}
}
#[derive(Debug, Error)]
#[error("rpc error: {message}")]
pub struct RpcError {
message: String,
}
impl RpcError {
pub fn new(message: String) -> Self {
Self { message }
}
}

View File

@ -1,2 +1,3 @@
pub mod errors;
pub mod types; pub mod types;
pub mod utils; pub mod utils;

View File

@ -1,3 +1,54 @@
use std::fmt::Display;
use serde::{de::Error, Deserialize};
use ssz_rs::Vector; use ssz_rs::Vector;
pub type Bytes32 = Vector<u8, 32>; pub type Bytes32 = Vector<u8, 32>;
#[derive(Debug)]
pub enum BlockTag {
Latest,
Finalized,
Number(u64),
}
impl Display for BlockTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let formatted = match self {
Self::Latest => "latest".to_string(),
Self::Finalized => "finalized".to_string(),
Self::Number(num) => num.to_string(),
};
write!(f, "{}", formatted)
}
}
impl<'de> Deserialize<'de> for BlockTag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let block: String = serde::Deserialize::deserialize(deserializer)?;
let parse_error = D::Error::custom("could not parse block tag");
let block_tag = match block.as_str() {
"latest" => BlockTag::Latest,
"finalized" => BlockTag::Finalized,
_ => match block.strip_prefix("0x") {
Some(hex_block) => {
let num = u64::from_str_radix(hex_block, 16).map_err(|_| parse_error)?;
BlockTag::Number(num)
}
None => {
let num = block.parse().map_err(|_| parse_error)?;
BlockTag::Number(num)
}
},
};
Ok(block_tag)
}
}

View File

@ -24,6 +24,7 @@ async-trait = "0.1.57"
log = "0.4.17" log = "0.4.17"
chrono = "0.4.22" chrono = "0.4.22"
openssl = { version = "0.10", features = ["vendored"] } openssl = { version = "0.10", features = ["vendored"] }
thiserror = "1.0.37"
common = { path = "../common" } common = { path = "../common" }
config = { path = "../config" } config = { path = "../config" }

View File

@ -5,7 +5,7 @@ use std::time::UNIX_EPOCH;
use blst::min_pk::{PublicKey, Signature}; use blst::min_pk::{PublicKey, Signature};
use blst::BLST_ERROR; use blst::BLST_ERROR;
use chrono::Duration; use chrono::Duration;
use eyre::{eyre, Result}; use eyre::Result;
use log::{debug, info}; use log::{debug, info};
use ssz_rs::prelude::*; use ssz_rs::prelude::*;
@ -13,6 +13,8 @@ use common::types::*;
use common::utils::*; use common::utils::*;
use config::Config; use config::Config;
use crate::errors::ConsensusError;
use super::rpc::Rpc; use super::rpc::Rpc;
use super::types::*; use super::types::*;
@ -49,12 +51,16 @@ impl<R: Rpc> ConsensusClient<R> {
&bootstrap.current_sync_committee_branch, &bootstrap.current_sync_committee_branch,
); );
let header_hash = bootstrap.header.hash_tree_root()?; let header_hash = bootstrap.header.hash_tree_root()?.to_string();
let header_valid = let expected_hash = format!("0x{}", hex::encode(checkpoint_block_root));
header_hash.to_string() == format!("0x{}", hex::encode(checkpoint_block_root)); let header_valid = header_hash == expected_hash;
if !(header_valid && committee_valid) { if !header_valid {
return Err(eyre!("Invalid Bootstrap")); return Err(ConsensusError::InvalidHeaderHash(expected_hash, header_hash).into());
}
if !committee_valid {
return Err(ConsensusError::InvalidCurrentSyncCommitteeProof.into());
} }
let store = Store { let store = Store {
@ -87,11 +93,15 @@ impl<R: Rpc> ConsensusClient<R> {
} else if slot == finalized_slot { } else if slot == finalized_slot {
self.store.finalized_header.clone().hash_tree_root()? self.store.finalized_header.clone().hash_tree_root()?
} else { } else {
return Err(eyre!("Block Not Found")); return Err(ConsensusError::PayloadNotFound(slot).into());
}; };
if verified_block_hash != block_hash { if verified_block_hash != block_hash {
Err(eyre!("Block Root Mismatch")) Err(ConsensusError::InvalidHeaderHash(
block_hash.to_string(),
verified_block_hash.to_string(),
)
.into())
} else { } else {
Ok(block.body.execution_payload) Ok(block.body.execution_payload)
} }
@ -156,7 +166,7 @@ impl<R: Rpc> ConsensusClient<R> {
fn verify_generic_update(&self, update: &GenericUpdate) -> Result<()> { fn verify_generic_update(&self, update: &GenericUpdate) -> Result<()> {
let bits = get_bits(&update.sync_aggregate.sync_committee_bits); let bits = get_bits(&update.sync_aggregate.sync_committee_bits);
if bits == 0 { if bits == 0 {
return Err(eyre!("Insufficient Participation")); return Err(ConsensusError::InsufficientParticipation.into());
} }
let update_finalized_slot = update.finalized_header.clone().unwrap_or_default().slot; let update_finalized_slot = update.finalized_header.clone().unwrap_or_default().slot;
@ -165,7 +175,7 @@ impl<R: Rpc> ConsensusClient<R> {
&& update.attested_header.slot >= update_finalized_slot; && update.attested_header.slot >= update_finalized_slot;
if !valid_time { if !valid_time {
return Err(eyre!("Invalid Timestamp")); return Err(ConsensusError::InvalidTimestamp.into());
} }
let store_period = calc_sync_period(self.store.finalized_header.slot); let store_period = calc_sync_period(self.store.finalized_header.slot);
@ -177,7 +187,7 @@ impl<R: Rpc> ConsensusClient<R> {
}; };
if !valid_period { if !valid_period {
return Err(eyre!("Invalid Period")); return Err(ConsensusError::InvalidPeriod.into());
} }
let update_attested_period = calc_sync_period(update.attested_header.slot); let update_attested_period = calc_sync_period(update.attested_header.slot);
@ -188,7 +198,7 @@ impl<R: Rpc> ConsensusClient<R> {
if update.attested_header.slot <= self.store.finalized_header.slot if update.attested_header.slot <= self.store.finalized_header.slot
&& !update_has_next_committee && !update_has_next_committee
{ {
return Err(eyre!("Update Not Relevent")); return Err(ConsensusError::NotRelevant.into());
} }
if update.finalized_header.is_some() && update.finality_branch.is_some() { if update.finalized_header.is_some() && update.finality_branch.is_some() {
@ -199,7 +209,7 @@ impl<R: Rpc> ConsensusClient<R> {
); );
if !is_valid { if !is_valid {
return Err(eyre!("Invalid Finality Proof")); return Err(ConsensusError::InvalidFinalityProof.into());
} }
} }
@ -211,7 +221,7 @@ impl<R: Rpc> ConsensusClient<R> {
); );
if !is_valid { if !is_valid {
return Err(eyre!("Invalid Next Sync Committee Proof")); return Err(ConsensusError::InvalidNextSyncCommitteeProof.into());
} }
} }
@ -232,7 +242,7 @@ impl<R: Rpc> ConsensusClient<R> {
let is_valid_sig = is_aggregate_valid(sig, signing_root.as_bytes(), &pks); let is_valid_sig = is_aggregate_valid(sig, signing_root.as_bytes(), &pks);
if !is_valid_sig { if !is_valid_sig {
return Err(eyre!("Invalid Signature")); return Err(ConsensusError::InvalidSignature.into());
} }
Ok(()) Ok(())
@ -631,6 +641,7 @@ mod tests {
use crate::{ use crate::{
consensus::calc_sync_period, consensus::calc_sync_period,
errors::ConsensusError,
rpc::{mock_rpc::MockRpc, Rpc}, rpc::{mock_rpc::MockRpc, Rpc},
types::Header, types::Header,
ConsensusClient, ConsensusClient,
@ -649,7 +660,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_verify_update() { async fn test_verify_update() {
let mut client = get_client().await; let client = get_client().await;
let period = calc_sync_period(client.store.finalized_header.slot); let period = calc_sync_period(client.store.finalized_header.slot);
let updates = client.rpc.get_updates(period).await.unwrap(); let updates = client.rpc.get_updates(period).await.unwrap();
@ -659,41 +670,50 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_verify_update_invalid_committee() { async fn test_verify_update_invalid_committee() {
let mut client = get_client().await; let client = get_client().await;
let period = calc_sync_period(client.store.finalized_header.slot); let period = calc_sync_period(client.store.finalized_header.slot);
let updates = client.rpc.get_updates(period).await.unwrap(); let updates = client.rpc.get_updates(period).await.unwrap();
let mut update = updates[0].clone(); let mut update = updates[0].clone();
update.next_sync_committee.pubkeys[0] = Vector::default(); update.next_sync_committee.pubkeys[0] = Vector::default();
let res = client.verify_update(&mut update); let err = client.verify_update(&mut update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidNextSyncCommitteeProof.to_string()
);
} }
#[tokio::test] #[tokio::test]
async fn test_verify_upadate_invlaid_finality() { async fn test_verify_upadate_invlaid_finality() {
let mut client = get_client().await; let client = get_client().await;
let period = calc_sync_period(client.store.finalized_header.slot); let period = calc_sync_period(client.store.finalized_header.slot);
let updates = client.rpc.get_updates(period).await.unwrap(); let updates = client.rpc.get_updates(period).await.unwrap();
let mut update = updates[0].clone(); let mut update = updates[0].clone();
update.finalized_header = Header::default(); update.finalized_header = Header::default();
let res = client.verify_update(&mut update); let err = client.verify_update(&mut update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidFinalityProof.to_string()
);
} }
#[tokio::test] #[tokio::test]
async fn test_verify_update_invalid_sig() { async fn test_verify_update_invalid_sig() {
let mut client = get_client().await; let client = get_client().await;
let period = calc_sync_period(client.store.finalized_header.slot); let period = calc_sync_period(client.store.finalized_header.slot);
let updates = client.rpc.get_updates(period).await.unwrap(); let updates = client.rpc.get_updates(period).await.unwrap();
let mut update = updates[0].clone(); let mut update = updates[0].clone();
update.sync_aggregate.sync_committee_signature = Vector::default(); update.sync_aggregate.sync_committee_signature = Vector::default();
let res = client.verify_update(&mut update); let err = client.verify_update(&mut update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidSignature.to_string()
);
} }
#[tokio::test] #[tokio::test]
@ -714,8 +734,11 @@ mod tests {
let mut update = client.rpc.get_finality_update().await.unwrap(); let mut update = client.rpc.get_finality_update().await.unwrap();
update.finalized_header = Header::default(); update.finalized_header = Header::default();
let res = client.verify_finality_update(&update); let err = client.verify_finality_update(&update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidFinalityProof.to_string()
);
} }
#[tokio::test] #[tokio::test]
@ -726,8 +749,11 @@ mod tests {
let mut update = client.rpc.get_finality_update().await.unwrap(); let mut update = client.rpc.get_finality_update().await.unwrap();
update.sync_aggregate.sync_committee_signature = Vector::default(); update.sync_aggregate.sync_committee_signature = Vector::default();
let res = client.verify_finality_update(&update); let err = client.verify_finality_update(&update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidSignature.to_string()
);
} }
#[tokio::test] #[tokio::test]
@ -747,7 +773,10 @@ mod tests {
let mut update = client.rpc.get_optimistic_update().await.unwrap(); let mut update = client.rpc.get_optimistic_update().await.unwrap();
update.sync_aggregate.sync_committee_signature = Vector::default(); update.sync_aggregate.sync_committee_signature = Vector::default();
let res = client.verify_optimistic_update(&update); let err = client.verify_optimistic_update(&update).err().unwrap();
assert!(res.is_err()); assert_eq!(
err.to_string(),
ConsensusError::InvalidSignature.to_string()
);
} }
} }

25
consensus/src/errors.rs Normal file
View File

@ -0,0 +1,25 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConsensusError {
#[error("insufficient participation")]
InsufficientParticipation,
#[error("invalid timestamp")]
InvalidTimestamp,
#[error("invalid sync committee period")]
InvalidPeriod,
#[error("update not relevant")]
NotRelevant,
#[error("invalid finality proof")]
InvalidFinalityProof,
#[error("invalid next sync committee proof")]
InvalidNextSyncCommitteeProof,
#[error("invalid current sync committee proof")]
InvalidCurrentSyncCommitteeProof,
#[error("invalid sync committee signature")]
InvalidSignature,
#[error("invalid header hash found: {0}, expected: {1}")]
InvalidHeaderHash(String, String),
#[error("payload not found for slot: {0}")]
PayloadNotFound(u64),
}

View File

@ -1,3 +1,4 @@
pub mod errors;
pub mod rpc; pub mod rpc;
pub mod types; pub mod types;

View File

@ -1,4 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use common::errors::RpcError;
use eyre::Result; use eyre::Result;
use super::Rpc; use super::Rpc;
@ -22,7 +23,14 @@ impl Rpc for NimbusRpc {
"{}/eth/v0/beacon/light_client/bootstrap/0x{}", "{}/eth/v0/beacon/light_client/bootstrap/0x{}",
self.rpc, root_hex self.rpc, root_hex
); );
let res = reqwest::get(req).await?.json::<BootstrapResponse>().await?;
let res = reqwest::get(req)
.await
.map_err(|e| RpcError::new(e.to_string()))?
.json::<BootstrapResponse>()
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(res.data.v) Ok(res.data.v)
} }
@ -31,34 +39,50 @@ impl Rpc for NimbusRpc {
"{}/eth/v0/beacon/light_client/updates?start_period={}&count=1000", "{}/eth/v0/beacon/light_client/updates?start_period={}&count=1000",
self.rpc, period self.rpc, period
); );
let res = reqwest::get(req).await?.json::<UpdateResponse>().await?;
let res = reqwest::get(req)
.await
.map_err(|e| RpcError::new(e.to_string()))?
.json::<UpdateResponse>()
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(res.data) Ok(res.data)
} }
async fn get_finality_update(&self) -> Result<FinalityUpdate> { async fn get_finality_update(&self) -> Result<FinalityUpdate> {
let req = format!("{}/eth/v0/beacon/light_client/finality_update", self.rpc); let req = format!("{}/eth/v0/beacon/light_client/finality_update", self.rpc);
let res = reqwest::get(req) let res = reqwest::get(req)
.await? .await
.map_err(|e| RpcError::new(e.to_string()))?
.json::<FinalityUpdateResponse>() .json::<FinalityUpdateResponse>()
.await?; .await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(res.data) Ok(res.data)
} }
async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> { async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> {
let req = format!("{}/eth/v0/beacon/light_client/optimistic_update", self.rpc); let req = format!("{}/eth/v0/beacon/light_client/optimistic_update", self.rpc);
let res = reqwest::get(req) let res = reqwest::get(req)
.await? .await
.map_err(|e| RpcError::new(e.to_string()))?
.json::<OptimisticUpdateResponse>() .json::<OptimisticUpdateResponse>()
.await?; .await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(res.data) Ok(res.data)
} }
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 = reqwest::get(req) let res = reqwest::get(req)
.await? .await
.map_err(|e| RpcError::new(e.to_string()))?
.json::<BeaconBlockResponse>() .json::<BeaconBlockResponse>()
.await?; .await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(res.data.message) Ok(res.data.message)
} }
} }

View File

@ -24,6 +24,7 @@ triehash-ethereum = { git = "https://github.com/openethereum/parity-ethereum" }
async-trait = "0.1.57" async-trait = "0.1.57"
openssl = { version = "0.10", features = ["vendored"] } openssl = { version = "0.10", features = ["vendored"] }
log = "0.4.17" log = "0.4.17"
thiserror = "1.0.37"
common = { path = "../common" } common = { path = "../common" }
consensus = { path = "../consensus" } consensus = { path = "../consensus" }

16
execution/src/errors.rs Normal file
View File

@ -0,0 +1,16 @@
use ethers::types::{Address, H256};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("invalid account proof for address: {0}")]
InvalidAccountProof(Address),
#[error("invalid storage proof for address: {0}, slot: {1}")]
InvalidStorageProof(Address, H256),
#[error("code hash mismatch for address: {0}, found: {1}, expected: {2}")]
CodeHashMismatch(Address, String, String),
#[error("receipt root mismatch for tx: {0}")]
ReceiptRootMismatch(String),
#[error("missing transaction for tx: {0}")]
MissingTransaction(String),
}

View File

@ -14,6 +14,8 @@ use futures::future::join_all;
use revm::KECCAK_EMPTY; use revm::KECCAK_EMPTY;
use triehash_ethereum::ordered_trie_root; use triehash_ethereum::ordered_trie_root;
use crate::errors::ExecutionError;
use super::proof::{encode_account, verify_proof}; use super::proof::{encode_account, verify_proof};
use super::rpc::Rpc; use super::rpc::Rpc;
use super::types::{Account, ExecutionBlock}; use super::types::{Account, ExecutionBlock};
@ -53,7 +55,7 @@ impl<R: Rpc> ExecutionClient<R> {
); );
if !is_valid { if !is_valid {
eyre::bail!("Invalid Proof"); return Err(ExecutionError::InvalidAccountProof(*address).into());
} }
let mut slot_map = HashMap::new(); let mut slot_map = HashMap::new();
@ -72,7 +74,9 @@ impl<R: Rpc> ExecutionClient<R> {
); );
if !is_valid { if !is_valid {
eyre::bail!("Invalid Proof"); return Err(
ExecutionError::InvalidStorageProof(*address, storage_proof.key).into(),
);
} }
slot_map.insert(storage_proof.key, storage_proof.value); slot_map.insert(storage_proof.key, storage_proof.value);
@ -85,7 +89,12 @@ impl<R: Rpc> ExecutionClient<R> {
let code_hash = keccak256(&code).into(); let code_hash = keccak256(&code).into();
if proof.code_hash != code_hash { if proof.code_hash != code_hash {
eyre::bail!("Invalid Proof"); return Err(ExecutionError::CodeHashMismatch(
*address,
code_hash.to_string(),
proof.code_hash.to_string(),
)
.into());
} }
code code
@ -183,7 +192,7 @@ impl<R: Rpc> ExecutionClient<R> {
let payload_receipt_root = H256::from_slice(&payload.receipts_root); let payload_receipt_root = H256::from_slice(&payload.receipts_root);
if expected_receipt_root != payload_receipt_root || !receipts.contains(&receipt) { if expected_receipt_root != payload_receipt_root || !receipts.contains(&receipt) {
return Err(eyre::eyre!("Receipt Proof Invalid")); return Err(ExecutionError::ReceiptRootMismatch(tx_hash.to_string()).into());
} }
Ok(Some(receipt)) Ok(Some(receipt))
@ -222,7 +231,7 @@ impl<R: Rpc> ExecutionClient<R> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !txs_encoded.contains(&tx_encoded) { if !txs_encoded.contains(&tx_encoded) {
return Err(eyre::eyre!("Transaction Proof Invalid")); return Err(ExecutionError::MissingTransaction(hash.to_string()).into());
} }
Ok(Some(tx)) Ok(Some(tx))

View File

@ -1,3 +1,4 @@
pub mod errors;
pub mod evm; pub mod evm;
pub mod rpc; pub mod rpc;
pub mod types; pub mod types;

View File

@ -1,6 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use async_trait::async_trait; use async_trait::async_trait;
use common::errors::RpcError;
use ethers::prelude::{Address, Http}; use ethers::prelude::{Address, Http};
use ethers::providers::{HttpRateLimitRetryPolicy, Middleware, Provider, RetryClient}; use ethers::providers::{HttpRateLimitRetryPolicy, Middleware, Provider, RetryClient};
use ethers::types::transaction::eip2718::TypedTransaction; use ethers::types::transaction::eip2718::TypedTransaction;
@ -51,7 +52,9 @@ impl Rpc for HttpRpc {
let proof_response = self let proof_response = self
.provider .provider
.get_proof(*address, slots.to_vec(), block) .get_proof(*address, slots.to_vec(), block)
.await?; .await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(proof_response) Ok(proof_response)
} }
@ -71,24 +74,44 @@ impl Rpc for HttpRpc {
.map(|data| Bytes::from(data.as_slice().to_owned())); .map(|data| Bytes::from(data.as_slice().to_owned()));
let tx = TypedTransaction::Eip1559(raw_tx); let tx = TypedTransaction::Eip1559(raw_tx);
let list = self.provider.create_access_list(&tx, block).await?; let list = self
.provider
.create_access_list(&tx, block)
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(list.access_list) Ok(list.access_list)
} }
async fn get_code(&self, address: &Address, block: u64) -> Result<Vec<u8>> { async fn get_code(&self, address: &Address, block: u64) -> Result<Vec<u8>> {
let block = Some(BlockId::from(block)); let block = Some(BlockId::from(block));
let code = self.provider.get_code(*address, block).await?; let code = self
.provider
.get_code(*address, block)
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(code.to_vec()) Ok(code.to_vec())
} }
async fn send_raw_transaction(&self, bytes: &Vec<u8>) -> Result<H256> { async fn send_raw_transaction(&self, bytes: &Vec<u8>) -> Result<H256> {
let bytes = Bytes::from(bytes.as_slice().to_owned()); let bytes = Bytes::from(bytes.as_slice().to_owned());
Ok(self.provider.send_raw_transaction(bytes).await?.tx_hash()) let tx = self
.provider
.send_raw_transaction(bytes)
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(tx.tx_hash())
} }
async fn get_transaction_receipt(&self, tx_hash: &H256) -> Result<Option<TransactionReceipt>> { async fn get_transaction_receipt(&self, tx_hash: &H256) -> Result<Option<TransactionReceipt>> {
let receipt = self.provider.get_transaction_receipt(*tx_hash).await?; let receipt = self
.provider
.get_transaction_receipt(*tx_hash)
.await
.map_err(|e| RpcError::new(e.to_string()))?;
Ok(receipt) Ok(receipt)
} }