Skip to content

Commit 06ed3c7

Browse files
authored
optimize storage and fix revert reason (#27)
* optimize storage and fix revert reason * audit changes * audit changes * ++ * fmt * fix tests * fix tests * changes * ++
1 parent fd1500f commit 06ed3c7

45 files changed

Lines changed: 900 additions & 297 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/testapp/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,8 @@ mod tests {
418418
token_account_id: AccountId,
419419
account_id: AccountId,
420420
) -> u128 {
421-
let mut key = token_account_id.as_bytes().to_vec();
421+
let mut key = vec![evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX];
422+
key.extend_from_slice(&token_account_id.as_bytes());
422423
key.push(1u8);
423424
key.extend(account_id.encode().expect("encode account id"));
424425

bin/testapp/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ mod tests {
315315
token_account_id: AccountId,
316316
account_id: AccountId,
317317
) -> u128 {
318-
let mut key = token_account_id.as_bytes().to_vec();
318+
let mut key = vec![evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX];
319+
key.extend_from_slice(&token_account_id.as_bytes());
319320
key.push(1u8); // Token::balances storage prefix
320321
key.extend(account_id.encode().expect("encode account id"));
321322

bin/testapp/tests/mempool_e2e.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -275,18 +275,21 @@ impl AsyncMockStorage {
275275

276276
/// Initialize an EthEoaAccount's storage (nonce and eth_address).
277277
fn init_eth_eoa_storage(&self, account_id: AccountId, eth_address: [u8; 20]) {
278-
// Storage keys are: account_id + prefix (u8)
278+
use evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX;
279+
// Storage keys are: ACCOUNT_STORAGE_PREFIX + account_id + prefix (u8)
279280
// Item::new(0) = nonce, Item::new(1) = eth_address
280281
let mut data = self.data.write().unwrap();
281282

282-
let mut nonce_key = account_id.as_bytes().to_vec();
283+
let mut nonce_key = vec![ACCOUNT_STORAGE_PREFIX];
284+
nonce_key.extend_from_slice(&account_id.as_bytes());
283285
nonce_key.push(0u8);
284286
data.insert(
285287
nonce_key,
286288
Message::new(&0u64).unwrap().into_bytes().unwrap(),
287289
);
288290

289-
let mut addr_key = account_id.as_bytes().to_vec();
291+
let mut addr_key = vec![ACCOUNT_STORAGE_PREFIX];
292+
addr_key.extend_from_slice(&account_id.as_bytes());
290293
addr_key.push(1u8);
291294
data.insert(
292295
addr_key,
@@ -296,18 +299,21 @@ impl AsyncMockStorage {
296299

297300
/// Initialize an Ed25519AuthAccount's storage (nonce and public key).
298301
fn init_ed25519_auth_storage(&self, account_id: AccountId, public_key: [u8; 32]) {
299-
// Storage keys are: account_id + prefix (u8)
302+
use evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX;
303+
// Storage keys are: ACCOUNT_STORAGE_PREFIX + account_id + prefix (u8)
300304
// Item::new(0) = nonce, Item::new(1) = public key
301305
let mut data = self.data.write().unwrap();
302306

303-
let mut nonce_key = account_id.as_bytes().to_vec();
307+
let mut nonce_key = vec![ACCOUNT_STORAGE_PREFIX];
308+
nonce_key.extend_from_slice(&account_id.as_bytes());
304309
nonce_key.push(0u8);
305310
data.insert(
306311
nonce_key,
307312
Message::new(&0u64).unwrap().into_bytes().unwrap(),
308313
);
309314

310-
let mut pubkey_key = account_id.as_bytes().to_vec();
315+
let mut pubkey_key = vec![ACCOUNT_STORAGE_PREFIX];
316+
pubkey_key.extend_from_slice(&account_id.as_bytes());
311317
pubkey_key.push(1u8);
312318
data.insert(
313319
pubkey_key,
@@ -317,7 +323,9 @@ impl AsyncMockStorage {
317323

318324
/// Set token balance directly in storage for a specific account.
319325
fn set_token_balance(&self, token_account_id: AccountId, account_id: AccountId, balance: u128) {
320-
let mut key = token_account_id.as_bytes().to_vec();
326+
use evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX;
327+
let mut key = vec![ACCOUNT_STORAGE_PREFIX];
328+
key.extend_from_slice(&token_account_id.as_bytes());
321329
key.push(1u8); // Token::balances storage prefix
322330
key.extend(account_id.encode().expect("encode account id"));
323331
let value = Message::new(&balance).unwrap().into_bytes().unwrap();
@@ -417,9 +425,11 @@ fn create_signed_tx(
417425
}
418426

419427
fn read_nonce<S: ReadonlyKV>(storage: &S, account_id: AccountId) -> u64 {
428+
use evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX;
420429
use evolve_core::Message;
421430

422-
let mut nonce_key = account_id.as_bytes().to_vec();
431+
let mut nonce_key = vec![ACCOUNT_STORAGE_PREFIX];
432+
nonce_key.extend_from_slice(&account_id.as_bytes());
423433
nonce_key.push(0u8);
424434
match storage.get(&nonce_key).expect("read nonce") {
425435
Some(value) => Message::from_bytes(value)
@@ -437,7 +447,8 @@ fn read_token_balance<S: ReadonlyKV>(
437447
use evolve_core::encoding::Encodable;
438448
use evolve_core::Message;
439449

440-
let mut key = token_account_id.as_bytes().to_vec();
450+
let mut key = vec![evolve_core::runtime_api::ACCOUNT_STORAGE_PREFIX];
451+
key.extend_from_slice(&token_account_id.as_bytes());
441452
key.push(1u8); // Token::balances storage prefix
442453
key.extend(account_id.encode().expect("encode account id"));
443454

bin/txload/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ struct Args {
2626
#[arg(long, default_value = "http://127.0.0.1:8545")]
2727
rpc_url: String,
2828

29-
/// Chain ID used for signing EIP-1559 transactions
30-
#[arg(long, default_value_t = 1)]
29+
/// Chain ID used for signing EIP-1559 transactions (default matches Evolve's DEFAULT_CHAIN_ID)
30+
#[arg(long, default_value_t = 900_901)]
3131
chain_id: u64,
3232

3333
/// Token account Ethereum address (0x...) that exposes `transfer`

crates/app/node/src/config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ pub struct ChainConfig {
124124

125125
impl Default for ChainConfig {
126126
fn default() -> Self {
127-
Self { chain_id: 1 }
127+
// 900_901 is deliberately not a live EVM network to prevent cross-chain replay.
128+
Self { chain_id: 900_901 }
128129
}
129130
}
130131

@@ -227,7 +228,7 @@ mod tests {
227228
.extract()
228229
.expect("figment extract failed");
229230

230-
assert_eq!(loaded.chain.chain_id, 1);
231+
assert_eq!(loaded.chain.chain_id, 900_901);
231232
assert_eq!(loaded.storage.path, DEFAULT_DATA_DIR);
232233
assert_eq!(loaded.rpc.http_addr, DEFAULT_RPC_ADDR);
233234
assert_eq!(loaded.grpc.addr, DEFAULT_GRPC_ADDR);

crates/app/node/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ type RuntimeContext = TokioContext;
223223
/// subsystem — all produced blocks must be persisted.
224224
async fn build_block_archive(context: TokioContext) -> OnBlockArchive {
225225
let config = BlockStorageConfig::default();
226+
let retention = config.retention_blocks;
227+
let prune_interval = config.blocks_per_section;
226228
let store = BlockStorage::new(context, config)
227229
.await
228230
.expect("failed to initialize block archive storage");
@@ -236,10 +238,18 @@ async fn build_block_archive(context: TokioContext) -> OnBlockArchive {
236238
if let Err(e) = store.put_sync(block_number, block_hash, block_bytes).await {
237239
tracing::warn!("Failed to archive block {}: {:?}", block_number, e);
238240
}
241+
242+
// Prune old blocks at section boundaries to bound disk usage.
243+
if retention > 0 && block_number > retention && block_number % prune_interval == 0 {
244+
let min_block = block_number.saturating_sub(retention);
245+
if let Err(e) = store.prune(min_block).await {
246+
tracing::warn!(min_block, "Failed to prune block archive: {:?}", e);
247+
}
248+
}
239249
}
240250
});
241251

242-
tracing::info!("Block archive storage enabled");
252+
tracing::info!(retention, "Block archive storage enabled");
243253

244254
Arc::new(move |block_number, block_hash, block_bytes| {
245255
let hash_bytes = ArchiveBlockHash::new(block_hash.0);

crates/app/sdk/collections/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ error-decode = ["linkme", "evolve_core/error-decode"]
2020

2121
[dev-dependencies]
2222
proptest = "1.4"
23+
evolve_testing = { workspace = true, features = ["proptest"] }

crates/app/sdk/collections/src/prop_tests.rs

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,13 @@ use crate::queue::Queue;
99
use crate::unordered_map::UnorderedMap;
1010
use crate::vector::Vector;
1111
use crate::ERR_EMPTY;
12+
use crate::ERR_NOT_FOUND;
13+
use evolve_testing::proptest_config::proptest_config;
1214
use proptest::prelude::*;
13-
use std::collections::{HashMap, VecDeque};
15+
use std::collections::{BTreeMap, HashMap, VecDeque};
1416

1517
const MAX_OPS: usize = 32;
1618
const MAX_KEYS: usize = 16;
17-
const DEFAULT_CASES: u32 = 128;
18-
const CI_CASES: u32 = 32;
19-
20-
fn proptest_cases() -> u32 {
21-
if let Ok(value) = std::env::var("EVOLVE_PROPTEST_CASES") {
22-
if let Ok(parsed) = value.parse::<u32>() {
23-
if parsed > 0 {
24-
return parsed;
25-
}
26-
}
27-
}
28-
29-
if std::env::var("EVOLVE_CI").is_ok() || std::env::var("CI").is_ok() {
30-
return CI_CASES;
31-
}
32-
33-
DEFAULT_CASES
34-
}
35-
36-
fn proptest_config() -> proptest::test_runner::Config {
37-
proptest::test_runner::Config {
38-
cases: proptest_cases(),
39-
..Default::default()
40-
}
41-
}
4219

4320
proptest! {
4421
#![proptest_config(proptest_config())]
@@ -216,3 +193,76 @@ proptest! {
216193
prop_assert_eq!(actual_pairs, expected_pairs);
217194
}
218195
}
196+
197+
// ============================================================================
198+
// Map model-based test
199+
// ============================================================================
200+
201+
#[derive(Clone, Debug)]
202+
enum MapOp {
203+
Set { key: u64, value: u64 },
204+
Get { key: u64 },
205+
Remove { key: u64 },
206+
Exists { key: u64 },
207+
}
208+
209+
fn map_ops_strategy() -> impl Strategy<Value = Vec<MapOp>> {
210+
let keys: Vec<u64> = (0..MAX_KEYS as u64).collect();
211+
212+
let set = (proptest::sample::select(keys.clone()), any::<u64>())
213+
.prop_map(|(key, value)| MapOp::Set { key, value });
214+
let get = proptest::sample::select(keys.clone()).prop_map(|key| MapOp::Get { key });
215+
let remove = proptest::sample::select(keys.clone()).prop_map(|key| MapOp::Remove { key });
216+
let exists = proptest::sample::select(keys).prop_map(|key| MapOp::Exists { key });
217+
218+
let op = prop_oneof![4 => set, 2 => get, 2 => remove, 1 => exists];
219+
proptest::collection::vec(op, 0..=MAX_OPS)
220+
}
221+
222+
proptest! {
223+
#![proptest_config(proptest_config())]
224+
225+
#[test]
226+
fn prop_map_matches_model(ops in map_ops_strategy()) {
227+
let map: Map<u64, u64> = Map::new(50);
228+
let mut env = MockEnvironment::new(1, 2);
229+
let mut model: BTreeMap<u64, u64> = BTreeMap::new();
230+
231+
for op in ops {
232+
match op {
233+
MapOp::Set { key, value } => {
234+
map.set(&key, &value, &mut env).unwrap();
235+
model.insert(key, value);
236+
}
237+
MapOp::Get { key } => {
238+
let actual = map.may_get(&key, &mut env).unwrap();
239+
let expected = model.get(&key).copied();
240+
prop_assert_eq!(actual, expected);
241+
242+
// Also verify get() returns ERR_NOT_FOUND for missing keys
243+
if expected.is_none() {
244+
prop_assert_eq!(map.get(&key, &mut env).unwrap_err(), ERR_NOT_FOUND);
245+
} else {
246+
prop_assert_eq!(map.get(&key, &mut env).unwrap(), expected.unwrap());
247+
}
248+
}
249+
MapOp::Remove { key } => {
250+
map.remove(&key, &mut env).unwrap();
251+
model.remove(&key);
252+
}
253+
MapOp::Exists { key } => {
254+
let actual = map.exists(&key, &mut env).unwrap();
255+
let expected = model.contains_key(&key);
256+
prop_assert_eq!(actual, expected);
257+
}
258+
}
259+
}
260+
261+
// Final state: verify all keys match the model
262+
for key in 0..MAX_KEYS as u64 {
263+
let expected = model.get(&key).copied();
264+
let actual = map.may_get(&key, &mut env).unwrap();
265+
prop_assert_eq!(actual, expected);
266+
}
267+
}
268+
}

crates/app/sdk/core/src/runtime_api.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{AccountId, InvokableMessage, InvokeRequest, InvokeResponse};
33
use borsh::{BorshDeserialize, BorshSerialize};
44
pub const ACCOUNT_IDENTIFIER_PREFIX: u8 = 0;
55
pub const ACCOUNT_IDENTIFIER_SINGLETON_PREFIX: u8 = 1;
6+
pub const ACCOUNT_STORAGE_PREFIX: u8 = 2;
67
pub const RUNTIME_ACCOUNT_ID: AccountId = AccountId::from_u64(0);
78

89
/// Storage key for consensus parameters.

crates/app/sdk/testing/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ rust-version.workspace = true
99
[dependencies]
1010
evolve_core.workspace = true
1111
evolve_stf_traits.workspace = true
12+
proptest = { version = "1.4", optional = true }
13+
14+
[features]
15+
default = []
16+
proptest = ["dep:proptest"]
1217

1318
[lints]
1419
workspace = true

0 commit comments

Comments
 (0)