Skip to content

Commit bb8e9e2

Browse files
authored
Merge pull request #78 from bitfinity-network/evm_state_recovery_api
[EPROD-564] EVMc state recovery API
2 parents 905965d + 573f791 commit bb8e9e2

5 files changed

Lines changed: 230 additions & 12 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ sha3 = "0.10"
6161
thiserror = "1.0"
6262
tokio = { version = "1.24", features = ["macros", "rt"] }
6363
zip = "0.6"
64+
murmur3 = "0.5"
6465
alloy-primitives = { version = "0.4", default-features = false }
6566

6667
[profile.dev]

src/did/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ sha3 = { workspace = true }
3333
thiserror = { workspace = true }
3434
once_cell = { workspace = true }
3535
alloy-primitives = { workspace = true }
36+
murmur3 = { workspace = true }
3637

3738
[dev-dependencies]
3839
eth-signer = { path = "../eth-signer" }

src/did/src/lib.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ pub mod integer;
1515
pub mod keccak;
1616
pub mod mint_order_exemption;
1717
pub mod notify;
18+
pub mod state;
1819
pub mod transaction;
1920

2021
pub mod fees;
2122
#[cfg(test)]
2223
mod test_utils;
2324

2425
pub use block::Block;
25-
use candid::{CandidType, Deserialize};
2626
pub use error::{ExitFatal, HaltError};
2727
pub use fees::FeeHistory;
2828
pub use gas::*;
@@ -33,11 +33,3 @@ pub use notify::NotificationInput;
3333
pub use transaction::{BlockNumber, Transaction, TransactionReceipt};
3434

3535
pub use crate::bytes::Bytes;
36-
37-
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, CandidType)]
38-
pub struct BasicAccount {
39-
/// Account balance.
40-
pub balance: U256,
41-
/// Account nonce.
42-
pub nonce: U256,
43-
}

src/did/src/state.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use std::borrow::Cow;
2+
use std::io::Cursor;
3+
use std::{fmt, mem};
4+
5+
use candid::CandidType;
6+
use ic_stable_structures::{Bound, Storable};
7+
use serde::{Deserialize, Serialize};
8+
9+
use crate::codec::ByteChunkReader;
10+
use crate::U256;
11+
12+
/// Describes basic state of an EVM account.
13+
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, CandidType)]
14+
pub struct BasicAccount {
15+
/// Account balance.
16+
pub balance: U256,
17+
/// Account nonce.
18+
pub nonce: U256,
19+
}
20+
21+
/// Action to update key-value state
22+
#[derive(Debug, CandidType, Deserialize, Clone)]
23+
pub enum StateUpdateAction<K, V> {
24+
Removed { key: K },
25+
Replace { key: K, value: V },
26+
}
27+
28+
/// StableDBStorage indices information
29+
#[derive(Debug, Clone, Serialize, CandidType, Deserialize, Eq, PartialEq)]
30+
pub struct Indices {
31+
/// Index of the current block
32+
pub pending_block: u64,
33+
/// Number of block to keep history
34+
pub history_size: u64,
35+
}
36+
37+
impl Indices {
38+
const STORABLE_BYTE_SIZE: usize = mem::size_of::<u64>() * 2;
39+
}
40+
41+
impl Storable for Indices {
42+
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
43+
let mut buf = Vec::with_capacity(Self::STORABLE_BYTE_SIZE);
44+
buf.extend_from_slice(&self.pending_block.to_be_bytes());
45+
buf.extend_from_slice(&self.history_size.to_be_bytes());
46+
buf.into()
47+
}
48+
49+
fn from_bytes(bytes: Cow<'_, [u8]>) -> Self {
50+
let mut reader = ByteChunkReader::new(&bytes);
51+
let pending_block = u64::from_be_bytes(*reader.read_slice());
52+
let history_size = u64::from_be_bytes(*reader.read_slice());
53+
Self {
54+
pending_block,
55+
history_size,
56+
}
57+
}
58+
59+
const BOUND: Bound = Bound::Bounded {
60+
max_size: Self::STORABLE_BYTE_SIZE as _,
61+
is_fixed_size: true,
62+
};
63+
}
64+
65+
/// Full information about entry
66+
#[derive(Clone, CandidType, Deserialize)]
67+
pub struct FullStorageValue {
68+
/// Data
69+
pub data: Vec<u8>,
70+
/// Number of inserts subtracted by number of removals.
71+
/// May be zero for the values which were removed in past before the moment they are cleaned.
72+
pub ref_count: u32,
73+
/// Index of the block when the item was removed last time (ref counter set to zeo)
74+
pub removed_at_block: u64,
75+
}
76+
77+
impl fmt::Debug for FullStorageValue {
78+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79+
f.debug_struct("FullStorageValue")
80+
.field("data_len", &self.data.len())
81+
.field("ref_count", &self.ref_count)
82+
.field("removed_at_block", &self.removed_at_block)
83+
.finish()
84+
}
85+
}
86+
87+
impl FullStorageValue {
88+
pub fn hash(&self) -> u128 {
89+
let mut all_data = Vec::with_capacity(
90+
self.data.len()
91+
+ mem::size_of_val(&self.ref_count)
92+
+ mem::size_of_val(&self.removed_at_block),
93+
);
94+
all_data.extend(&self.data);
95+
all_data.extend(self.ref_count.to_le_bytes());
96+
all_data.extend(self.removed_at_block.to_le_bytes());
97+
98+
murmur3::murmur3_x86_128(&mut Cursor::new(&all_data), 0).expect("should calculate hash")
99+
}
100+
}

src/evm-canister-client/src/client.rs

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use candid::Principal;
22
use did::block::{BlockResult, ExeResult};
33
use did::error::Result;
4+
use did::state::{BasicAccount, FullStorageValue, Indices, StateUpdateAction};
45
use did::{
5-
BasicAccount, Block, BlockNumber, Bytes, EstimateGasRequest, Transaction, TransactionReceipt,
6-
H160, H256, U256,
6+
Block, BlockNumber, Bytes, EstimateGasRequest, Transaction, TransactionReceipt, H160, H256,
7+
U256,
78
};
89
use ic_canister_client::{CanisterClient, CanisterClientResult};
910

1011
use crate::EvmResult;
1112

12-
type BlockWithData = Vec<(Block<H256>, Vec<(Transaction, ExeResult)>)>;
13+
pub type BlockWithData = Vec<(Block<H256>, Vec<(Transaction, ExeResult)>)>;
1314

1415
/// An EVM canister client.
1516
#[derive(Debug)]
@@ -455,6 +456,129 @@ impl<C: CanisterClient> EvmCanisterClient<C> {
455456
.await
456457
}
457458

459+
/// Returns requested part of low-level representation of EVM state.
460+
/// Supports pagination.
461+
///
462+
/// # Arguments
463+
///
464+
/// * `prev_key` - returned keys will be `key > prev_key` if provided.
465+
/// * `limit` - maximum number of keys to return.
466+
///
467+
/// # Returns
468+
///
469+
/// - Vector of ordered pairs `(storage_key, storage_value_hash)` with `len <= limit`.
470+
/// - First `storage_key > prev_key` if `prev_key` is `Some(_)`.
471+
///
472+
/// # Errors
473+
///
474+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
475+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
476+
pub async fn get_state_storage_item_hashes(
477+
&self,
478+
prev_key: Option<H256>,
479+
limit: u32,
480+
) -> CanisterClientResult<EvmResult<Vec<(H256, u128)>>> {
481+
self.client
482+
.query("get_state_storage_item_hashes", (prev_key, limit))
483+
.await
484+
}
485+
486+
/// Applies the given list of low-level state changes.
487+
///
488+
/// # Arguments
489+
///
490+
/// * `actions` - list of operations should be applied.
491+
///
492+
/// # Errors
493+
///
494+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
495+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
496+
pub async fn apply_state_storage_changes(
497+
&self,
498+
actions: Vec<StateUpdateAction<H256, FullStorageValue>>,
499+
) -> CanisterClientResult<EvmResult<()>> {
500+
self.client
501+
.update("apply_state_storage_changes", (actions,))
502+
.await
503+
}
504+
505+
/// Returns requested part of low-level representation of EVM clear info.
506+
/// Supports pagination.
507+
///
508+
/// # Arguments
509+
///
510+
/// * `prev_key` - returned keys will be `key > prev_key` if provided.
511+
/// * `limit` - maximum number of keys to return.
512+
///
513+
/// # Returns
514+
///
515+
/// - Vector of ordered pairs `(key_1, key_2)` with `len <= limit`.
516+
/// - First pair `(key_1, key_2) > prev_key` if `prev_key` is `Some(_)`.
517+
///
518+
/// # Errors
519+
///
520+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
521+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
522+
pub async fn get_clear_info_entries(
523+
&self,
524+
prev_key: Option<(u64, H256)>,
525+
limit: u32,
526+
) -> CanisterClientResult<EvmResult<Vec<(u64, H256)>>> {
527+
self.client
528+
.query("get_clear_info_entries", (prev_key, limit))
529+
.await
530+
}
531+
532+
/// Applies the given list of low-level clear info changes.
533+
///
534+
/// # Arguments
535+
///
536+
/// * `actions` - list of operations should be applied.
537+
///
538+
/// # Errors
539+
///
540+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
541+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
542+
pub async fn apply_clear_info_changes(
543+
&self,
544+
actions: Vec<StateUpdateAction<(u64, H256), ()>>,
545+
) -> CanisterClientResult<EvmResult<()>> {
546+
self.client
547+
.update("apply_clear_info_changes", (actions,))
548+
.await
549+
}
550+
551+
/// Sets low-level storage indices.
552+
///
553+
/// # Arguments
554+
///
555+
/// * `indices` - indices to set.
556+
///
557+
/// # Errors
558+
///
559+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
560+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
561+
pub async fn set_storage_indices(
562+
&self,
563+
indices: Indices,
564+
) -> CanisterClientResult<EvmResult<()>> {
565+
self.client.update("set_storage_indices", (indices,)).await
566+
}
567+
568+
/// Sets state root hash.
569+
///
570+
/// # Arguments
571+
///
572+
/// * `root` - root to set.
573+
///
574+
/// # Errors
575+
///
576+
/// - If evm-canister not disabled, returns `EvmError::Internal(msg)`;
577+
/// - If caller have not `Permission::UpdateBlockchain` permission, returns `EvmError::Unauthorized`;
578+
pub async fn set_state_root(&self, root: H256) -> CanisterClientResult<EvmResult<()>> {
579+
self.client.update("set_state_root", (root,)).await
580+
}
581+
458582
/// Updates the runtime configuration of the logger with a new filter in the same form as the `RUST_LOG`
459583
/// environment variable.
460584
/// Example of valid filters:

0 commit comments

Comments
 (0)