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:
parent
f3b9750eff
commit
5d1f4a6344
|
@ -513,6 +513,7 @@ dependencies = [
|
|||
"openssl",
|
||||
"serde",
|
||||
"ssz-rs",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
|
@ -551,6 +552,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"ssz-rs",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
]
|
||||
|
@ -1040,6 +1042,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"ssz-rs",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
"triehash-ethereum",
|
||||
|
@ -3285,18 +3288,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.34"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.34"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -4,6 +4,7 @@ use ethers::prelude::{Address, U256};
|
|||
use ethers::types::{Transaction, TransactionReceipt, H256};
|
||||
use eyre::{eyre, Result};
|
||||
|
||||
use common::types::BlockTag;
|
||||
use config::Config;
|
||||
use consensus::types::Header;
|
||||
use execution::types::{CallOpts, ExecutionBlock};
|
||||
|
@ -13,7 +14,7 @@ use tokio::sync::RwLock;
|
|||
use tokio::time::sleep;
|
||||
|
||||
use crate::database::{Database, FileDB};
|
||||
use crate::node::{BlockTag, Node};
|
||||
use crate::node::Node;
|
||||
use crate::rpc::Rpc;
|
||||
|
||||
pub struct Client<DB: Database> {
|
||||
|
@ -49,13 +50,13 @@ impl<DB: Database> Client<DB> {
|
|||
spawn(async move {
|
||||
let res = node.write().await.sync().await;
|
||||
if let Err(err) = res {
|
||||
warn!("{}", err);
|
||||
warn!("consensus error: {}", err);
|
||||
}
|
||||
|
||||
loop {
|
||||
let res = node.write().await.advance().await;
|
||||
if let Err(err) = res {
|
||||
warn!("{}", err);
|
||||
warn!("consensus error: {}", err);
|
||||
}
|
||||
|
||||
let next_update = node.read().await.duration_until_next_update();
|
||||
|
|
|
@ -6,6 +6,8 @@ use ethers::prelude::{Address, U256};
|
|||
use ethers::types::{Transaction, TransactionReceipt, H256};
|
||||
use eyre::{eyre, Result};
|
||||
|
||||
use common::errors::BlockNotFoundError;
|
||||
use common::types::BlockTag;
|
||||
use config::Config;
|
||||
use consensus::rpc::nimbus_rpc::NimbusRpc;
|
||||
use consensus::types::{ExecutionPayload, Header};
|
||||
|
@ -147,7 +149,7 @@ impl Node {
|
|||
let value = account.slots.get(&slot);
|
||||
match 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 {
|
||||
BlockTag::Latest => {
|
||||
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 => {
|
||||
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) => {
|
||||
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;
|
||||
|
||||
if slot_delay > 10 {
|
||||
return Err(eyre!("Out of Sync"));
|
||||
return Err(eyre!("out of sync"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -263,9 +267,3 @@ impl Node {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BlockTag {
|
||||
Latest,
|
||||
Finalized,
|
||||
Number(u64),
|
||||
}
|
||||
|
|
|
@ -13,9 +13,12 @@ use jsonrpsee::{
|
|||
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};
|
||||
|
||||
pub struct Rpc {
|
||||
|
@ -47,16 +50,16 @@ impl Rpc {
|
|||
}
|
||||
}
|
||||
|
||||
#[rpc(client, server, namespace = "eth")]
|
||||
#[rpc(server, namespace = "eth")]
|
||||
trait EthRpc {
|
||||
#[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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
async fn estimate_gas(&self, opts: CallOpts) -> Result<String, Error>;
|
||||
#[method(name = "chainId")]
|
||||
|
@ -70,7 +73,7 @@ trait EthRpc {
|
|||
#[method(name = "getBlockByNumber")]
|
||||
async fn get_block_by_number(
|
||||
&self,
|
||||
num: &str,
|
||||
block: BlockTag,
|
||||
full_tx: bool,
|
||||
) -> Result<Option<ExecutionBlock>, Error>;
|
||||
#[method(name = "getBlockByHash")]
|
||||
|
@ -104,9 +107,8 @@ struct RpcInner {
|
|||
|
||||
#[async_trait]
|
||||
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");
|
||||
let block = convert_err(decode_block(block))?;
|
||||
let address = convert_err(Address::from_str(address))?;
|
||||
let node = self.node.read().await;
|
||||
let balance = convert_err(node.get_balance(&address, &block).await)?;
|
||||
|
@ -114,8 +116,7 @@ impl EthRpcServer for RpcInner {
|
|||
Ok(balance.encode_hex())
|
||||
}
|
||||
|
||||
async fn get_transaction_count(&self, address: &str, block: &str) -> Result<String, Error> {
|
||||
let block = convert_err(decode_block(block))?;
|
||||
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)?;
|
||||
|
@ -123,8 +124,7 @@ impl EthRpcServer for RpcInner {
|
|||
Ok(nonce.encode_hex())
|
||||
}
|
||||
|
||||
async fn get_code(&self, address: &str, block: &str) -> Result<String, Error> {
|
||||
let block = convert_err(decode_block(block))?;
|
||||
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)?;
|
||||
|
@ -132,9 +132,8 @@ impl EthRpcServer for RpcInner {
|
|||
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");
|
||||
let block = convert_err(decode_block(block))?;
|
||||
let node = self.node.read().await;
|
||||
let res = convert_err(node.call(&opts, &block))?;
|
||||
|
||||
|
@ -175,10 +174,9 @@ impl EthRpcServer for RpcInner {
|
|||
|
||||
async fn get_block_by_number(
|
||||
&self,
|
||||
block: &str,
|
||||
block: BlockTag,
|
||||
_full_tx: bool,
|
||||
) -> Result<Option<ExecutionBlock>, Error> {
|
||||
let block = convert_err(decode_block(block))?;
|
||||
let node = self.node.read().await;
|
||||
let block = convert_err(node.get_block_by_number(&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())
|
||||
})
|
||||
}
|
||||
|
||||
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()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,3 +13,4 @@ ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs" }
|
|||
ethers = "0.17.0"
|
||||
toml = "0.5.9"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
thiserror = "1.0.37"
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod errors;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1,3 +1,54 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use serde::{de::Error, Deserialize};
|
||||
use ssz_rs::Vector;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ async-trait = "0.1.57"
|
|||
log = "0.4.17"
|
||||
chrono = "0.4.22"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
thiserror = "1.0.37"
|
||||
|
||||
common = { path = "../common" }
|
||||
config = { path = "../config" }
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::time::UNIX_EPOCH;
|
|||
use blst::min_pk::{PublicKey, Signature};
|
||||
use blst::BLST_ERROR;
|
||||
use chrono::Duration;
|
||||
use eyre::{eyre, Result};
|
||||
use eyre::Result;
|
||||
use log::{debug, info};
|
||||
use ssz_rs::prelude::*;
|
||||
|
||||
|
@ -13,6 +13,8 @@ use common::types::*;
|
|||
use common::utils::*;
|
||||
use config::Config;
|
||||
|
||||
use crate::errors::ConsensusError;
|
||||
|
||||
use super::rpc::Rpc;
|
||||
use super::types::*;
|
||||
|
||||
|
@ -49,12 +51,16 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
&bootstrap.current_sync_committee_branch,
|
||||
);
|
||||
|
||||
let header_hash = bootstrap.header.hash_tree_root()?;
|
||||
let header_valid =
|
||||
header_hash.to_string() == format!("0x{}", hex::encode(checkpoint_block_root));
|
||||
let header_hash = bootstrap.header.hash_tree_root()?.to_string();
|
||||
let expected_hash = format!("0x{}", hex::encode(checkpoint_block_root));
|
||||
let header_valid = header_hash == expected_hash;
|
||||
|
||||
if !(header_valid && committee_valid) {
|
||||
return Err(eyre!("Invalid Bootstrap"));
|
||||
if !header_valid {
|
||||
return Err(ConsensusError::InvalidHeaderHash(expected_hash, header_hash).into());
|
||||
}
|
||||
|
||||
if !committee_valid {
|
||||
return Err(ConsensusError::InvalidCurrentSyncCommitteeProof.into());
|
||||
}
|
||||
|
||||
let store = Store {
|
||||
|
@ -87,11 +93,15 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
} else if slot == finalized_slot {
|
||||
self.store.finalized_header.clone().hash_tree_root()?
|
||||
} else {
|
||||
return Err(eyre!("Block Not Found"));
|
||||
return Err(ConsensusError::PayloadNotFound(slot).into());
|
||||
};
|
||||
|
||||
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 {
|
||||
Ok(block.body.execution_payload)
|
||||
}
|
||||
|
@ -156,7 +166,7 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
fn verify_generic_update(&self, update: &GenericUpdate) -> Result<()> {
|
||||
let bits = get_bits(&update.sync_aggregate.sync_committee_bits);
|
||||
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;
|
||||
|
@ -165,7 +175,7 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
&& update.attested_header.slot >= update_finalized_slot;
|
||||
|
||||
if !valid_time {
|
||||
return Err(eyre!("Invalid Timestamp"));
|
||||
return Err(ConsensusError::InvalidTimestamp.into());
|
||||
}
|
||||
|
||||
let store_period = calc_sync_period(self.store.finalized_header.slot);
|
||||
|
@ -177,7 +187,7 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
};
|
||||
|
||||
if !valid_period {
|
||||
return Err(eyre!("Invalid Period"));
|
||||
return Err(ConsensusError::InvalidPeriod.into());
|
||||
}
|
||||
|
||||
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
|
||||
&& !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() {
|
||||
|
@ -199,7 +209,7 @@ impl<R: Rpc> ConsensusClient<R> {
|
|||
);
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
if !is_valid_sig {
|
||||
return Err(eyre!("Invalid Signature"));
|
||||
return Err(ConsensusError::InvalidSignature.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -631,6 +641,7 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
consensus::calc_sync_period,
|
||||
errors::ConsensusError,
|
||||
rpc::{mock_rpc::MockRpc, Rpc},
|
||||
types::Header,
|
||||
ConsensusClient,
|
||||
|
@ -649,7 +660,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
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 updates = client.rpc.get_updates(period).await.unwrap();
|
||||
|
||||
|
@ -659,41 +670,50 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
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 updates = client.rpc.get_updates(period).await.unwrap();
|
||||
|
||||
let mut update = updates[0].clone();
|
||||
update.next_sync_committee.pubkeys[0] = Vector::default();
|
||||
|
||||
let res = client.verify_update(&mut update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_update(&mut update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidNextSyncCommitteeProof.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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 updates = client.rpc.get_updates(period).await.unwrap();
|
||||
|
||||
let mut update = updates[0].clone();
|
||||
update.finalized_header = Header::default();
|
||||
|
||||
let res = client.verify_update(&mut update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_update(&mut update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidFinalityProof.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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 updates = client.rpc.get_updates(period).await.unwrap();
|
||||
|
||||
let mut update = updates[0].clone();
|
||||
update.sync_aggregate.sync_committee_signature = Vector::default();
|
||||
|
||||
let res = client.verify_update(&mut update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_update(&mut update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidSignature.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -714,8 +734,11 @@ mod tests {
|
|||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||
update.finalized_header = Header::default();
|
||||
|
||||
let res = client.verify_finality_update(&update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_finality_update(&update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidFinalityProof.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -726,8 +749,11 @@ mod tests {
|
|||
let mut update = client.rpc.get_finality_update().await.unwrap();
|
||||
update.sync_aggregate.sync_committee_signature = Vector::default();
|
||||
|
||||
let res = client.verify_finality_update(&update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_finality_update(&update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidSignature.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -747,7 +773,10 @@ mod tests {
|
|||
let mut update = client.rpc.get_optimistic_update().await.unwrap();
|
||||
update.sync_aggregate.sync_committee_signature = Vector::default();
|
||||
|
||||
let res = client.verify_optimistic_update(&update);
|
||||
assert!(res.is_err());
|
||||
let err = client.verify_optimistic_update(&update).err().unwrap();
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
ConsensusError::InvalidSignature.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod errors;
|
||||
pub mod rpc;
|
||||
pub mod types;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use async_trait::async_trait;
|
||||
use common::errors::RpcError;
|
||||
use eyre::Result;
|
||||
|
||||
use super::Rpc;
|
||||
|
@ -22,7 +23,14 @@ impl Rpc for NimbusRpc {
|
|||
"{}/eth/v0/beacon/light_client/bootstrap/0x{}",
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -31,34 +39,50 @@ impl Rpc for NimbusRpc {
|
|||
"{}/eth/v0/beacon/light_client/updates?start_period={}&count=1000",
|
||||
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)
|
||||
}
|
||||
|
||||
async fn get_finality_update(&self) -> Result<FinalityUpdate> {
|
||||
let req = format!("{}/eth/v0/beacon/light_client/finality_update", self.rpc);
|
||||
let res = reqwest::get(req)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?
|
||||
.json::<FinalityUpdateResponse>()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?;
|
||||
|
||||
Ok(res.data)
|
||||
}
|
||||
|
||||
async fn get_optimistic_update(&self) -> Result<OptimisticUpdate> {
|
||||
let req = format!("{}/eth/v0/beacon/light_client/optimistic_update", self.rpc);
|
||||
let res = reqwest::get(req)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?
|
||||
.json::<OptimisticUpdateResponse>()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?;
|
||||
|
||||
Ok(res.data)
|
||||
}
|
||||
|
||||
async fn get_block(&self, slot: u64) -> Result<BeaconBlock> {
|
||||
let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot);
|
||||
let res = reqwest::get(req)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?
|
||||
.json::<BeaconBlockResponse>()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?;
|
||||
|
||||
Ok(res.data.message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ triehash-ethereum = { git = "https://github.com/openethereum/parity-ethereum" }
|
|||
async-trait = "0.1.57"
|
||||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
log = "0.4.17"
|
||||
thiserror = "1.0.37"
|
||||
|
||||
common = { path = "../common" }
|
||||
consensus = { path = "../consensus" }
|
||||
|
|
|
@ -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),
|
||||
}
|
|
@ -14,6 +14,8 @@ use futures::future::join_all;
|
|||
use revm::KECCAK_EMPTY;
|
||||
use triehash_ethereum::ordered_trie_root;
|
||||
|
||||
use crate::errors::ExecutionError;
|
||||
|
||||
use super::proof::{encode_account, verify_proof};
|
||||
use super::rpc::Rpc;
|
||||
use super::types::{Account, ExecutionBlock};
|
||||
|
@ -53,7 +55,7 @@ impl<R: Rpc> ExecutionClient<R> {
|
|||
);
|
||||
|
||||
if !is_valid {
|
||||
eyre::bail!("Invalid Proof");
|
||||
return Err(ExecutionError::InvalidAccountProof(*address).into());
|
||||
}
|
||||
|
||||
let mut slot_map = HashMap::new();
|
||||
|
@ -72,7 +74,9 @@ impl<R: Rpc> ExecutionClient<R> {
|
|||
);
|
||||
|
||||
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);
|
||||
|
@ -85,7 +89,12 @@ impl<R: Rpc> ExecutionClient<R> {
|
|||
let code_hash = keccak256(&code).into();
|
||||
|
||||
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
|
||||
|
@ -183,7 +192,7 @@ impl<R: Rpc> ExecutionClient<R> {
|
|||
let payload_receipt_root = H256::from_slice(&payload.receipts_root);
|
||||
|
||||
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))
|
||||
|
@ -222,7 +231,7 @@ impl<R: Rpc> ExecutionClient<R> {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
if !txs_encoded.contains(&tx_encoded) {
|
||||
return Err(eyre::eyre!("Transaction Proof Invalid"));
|
||||
return Err(ExecutionError::MissingTransaction(hash.to_string()).into());
|
||||
}
|
||||
|
||||
Ok(Some(tx))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod errors;
|
||||
pub mod evm;
|
||||
pub mod rpc;
|
||||
pub mod types;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common::errors::RpcError;
|
||||
use ethers::prelude::{Address, Http};
|
||||
use ethers::providers::{HttpRateLimitRetryPolicy, Middleware, Provider, RetryClient};
|
||||
use ethers::types::transaction::eip2718::TypedTransaction;
|
||||
|
@ -51,7 +52,9 @@ impl Rpc for HttpRpc {
|
|||
let proof_response = self
|
||||
.provider
|
||||
.get_proof(*address, slots.to_vec(), block)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| RpcError::new(e.to_string()))?;
|
||||
|
||||
Ok(proof_response)
|
||||
}
|
||||
|
||||
|
@ -71,24 +74,44 @@ impl Rpc for HttpRpc {
|
|||
.map(|data| Bytes::from(data.as_slice().to_owned()));
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
async fn get_code(&self, address: &Address, block: u64) -> Result<Vec<u8>> {
|
||||
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())
|
||||
}
|
||||
|
||||
async fn send_raw_transaction(&self, bytes: &Vec<u8>) -> Result<H256> {
|
||||
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>> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue