Skip to content

Commit

Permalink
feat: better test structure
Browse files Browse the repository at this point in the history
  • Loading branch information
hai-rise committed Apr 26, 2024
1 parent bdd17fa commit d394329
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cargo run --release
- run: cargo test --release
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
dashmap = "5.5.3"
rand = "0.8.5"
# TODO: Let's do our best to port needed changes upstream
revm = { git = "https://github.com/risechain/revm", rev = "3c046a6aa734d914dc37bcade8ac5932a9599a23" }

Expand Down
11 changes: 3 additions & 8 deletions src/block_stm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,17 @@ impl BlockSTM {
/// Run a list of REVM transactions through Block-STM.
/// TODO: Better concurrency control
pub fn run(
storage: Arc<Storage>,
storage: Storage,
block_env: BlockEnv,
txs: Arc<Vec<TxEnv>>,
txs: Vec<TxEnv>,
concurrency_level: NonZeroUsize,
) -> Vec<ResultAndState> {
let block_size = txs.len();
let scheduler = Scheduler::new(block_size);
let mv_memory = Arc::new(MvMemory::new(block_size));
let vm = Vm::new(
storage.clone(),
block_env.clone(),
txs.clone(),
mv_memory.clone(),
);
// TODO: Better error handling
let mut beneficiary_account_info = storage.basic(block_env.coinbase).unwrap();
let vm = Vm::new(storage, block_env.clone(), txs, mv_memory.clone());
// TODO: Should we move this to `Vm`?
let execution_results = (0..block_size).map(|_| Mutex::new(None)).collect();
// TODO: Better thread handling
Expand Down
83 changes: 0 additions & 83 deletions src/main.rs

This file was deleted.

12 changes: 7 additions & 5 deletions src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ impl Database for VmDb {
}

// The VM describes how to read values to execute transactions. Also, it
// captures the read & write sets of each execution.
// captures the read & write sets of each execution. Note that a single
// `Vm` can be shared among threads.
pub(crate) struct Vm {
storage: Arc<Storage>,
block_env: BlockEnv,
Expand All @@ -200,15 +201,16 @@ pub(crate) struct Vm {

impl Vm {
pub(crate) fn new(
storage: Arc<Storage>,
storage: Storage,
block_env: BlockEnv,
txs: Arc<Vec<TxEnv>>,
txs: Vec<TxEnv>,
// TODO: Make `Vm` own `MvMemory` away from `BlockSTM::run`?
mv_memory: Arc<MvMemory>,
) -> Self {
Self {
storage,
storage: Arc::new(storage),
block_env,
txs,
txs: Arc::new(txs),
mv_memory,
}
}
Expand Down
38 changes: 38 additions & 0 deletions tests/beneficiary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Tests for the beneficiary account, especially for the lazy update of its balance to avoid
// "implicit" dependency among consecutive transactions.
// Currently, we randomly insert a beneficiary spending in the middle of the block.
// TODO: Add more test scenarios around the beneficiary account's activities in the block.

use rand::random;
use revm::primitives::{alloy_primitives::U160, env::TxEnv, Address, BlockEnv, TransactTo, U256};

mod common;

#[test]
fn beneficiary() {
let block_size = 100_000; // number of transactions
let block_env = BlockEnv::default();

common::test_txs(
block_env,
// Mock `block_size` transactions sending some tokens to itself.
// Skipping `Address::ZERO` as the beneficiary account.
(1..=block_size)
.map(|i| {
// Randomly insert a beneficiary spending every ~256 txs
let address = if random::<u8>() == 0 {
Address::from(U160::from(0))
} else {
Address::from(U160::from(i))
};
TxEnv {
caller: address,
transact_to: TransactTo::Call(address),
value: U256::from(1),
gas_price: U256::from(1),
..TxEnv::default()
}
})
.collect(),
);
}
70 changes: 70 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::{num::NonZeroUsize, thread};

use block_stm_revm::{BlockSTM, Storage};
use revm::{
primitives::{
alloy_primitives::U160, AccountInfo, Address, BlockEnv, ResultAndState, TxEnv, U256,
},
DatabaseCommit, Evm, InMemoryDB,
};

// Return an `InMemoryDB` for sequential usage and `Storage` for BlockSTM usage.
// Both represent a "standard" mock state with prefilled accounts.
// TODO: Mock pre-deployed contracts.
fn mock_dbs(num_prefilled_accounts: usize) -> (InMemoryDB, Storage) {
let mut sequential_db = InMemoryDB::default();
let mut block_stm_storage = Storage::default();

// Mock the beneficiary account (`Address:ZERO`) and the next `block_size` user accounts.
// Filling half full accounts to have enough tokens for tests without worrying about the
// corner case of balance not going beyond `U256::MAX`.
let mock_account = AccountInfo::from_balance(U256::MAX.div_ceil(U256::from(2)));
for i in 0..=num_prefilled_accounts {
let address = Address::from(U160::from(i));
sequential_db.insert_account_info(address, mock_account.clone());
block_stm_storage.insert_account_info(address, mock_account.clone());
}

(sequential_db, block_stm_storage)
}

// The source-of-truth sequential execution result that BlockSTM must match.
fn execute_sequential(
mut db: InMemoryDB,
block_env: BlockEnv,
txs: &[TxEnv],
) -> Vec<ResultAndState> {
txs.iter()
.map(|tx| {
let result_and_state = Evm::builder()
.with_ref_db(&mut db)
.with_block_env(block_env.clone())
.with_tx_env(tx.clone())
.build()
.transact()
// TODO: Proper error handling
.unwrap();
db.commit(result_and_state.state.clone());
result_and_state
})
.collect()
}

// Execute a list of transactions sequentially & with BlockSTM and assert that
// the execution results match.
pub(crate) fn test_txs(block_env: BlockEnv, txs: Vec<TxEnv>) {
// TODO: Decouple the (number of) prefilled accounts with the number of transactions.
let (sequential_db, block_stm_storage) = mock_dbs(txs.len());
let result_sequential = execute_sequential(sequential_db, block_env.clone(), &txs);
let result_block_stm = BlockSTM::run(
block_stm_storage,
block_env,
txs,
thread::available_parallelism().unwrap_or(NonZeroUsize::MIN),
);

assert_eq!(
result_sequential, result_block_stm,
"Block-STM's execution result doesn't match Sequential's"
);
}
31 changes: 31 additions & 0 deletions tests/raw_transfers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Test raw transfers -- only send some ETH from one account to another without extra data.
// Currently, we only have a no-state-conflict test of user account sending to themselves.
// TODO: Add more tests of accounts cross-transferring to create state depdendencies.

use revm::primitives::{alloy_primitives::U160, env::TxEnv, Address, BlockEnv, TransactTo, U256};

mod common;

#[test]
fn raw_transfers() {
let block_size = 100_000; // number of transactions
let block_env = BlockEnv::default();

common::test_txs(
block_env,
// Mock `block_size` transactions sending some tokens to itself.
// Skipping `Address::ZERO` as the beneficiary account.
(1..=block_size)
.map(|i| {
let address = Address::from(U160::from(i));
TxEnv {
caller: address,
transact_to: TransactTo::Call(address),
value: U256::from(1),
gas_price: U256::from(1),
..TxEnv::default()
}
})
.collect(),
);
}

0 comments on commit d394329

Please sign in to comment.