Skip to content

Commit

Permalink
feat: lazy update beneficiary balance to avoid "implicit" dependency
Browse files Browse the repository at this point in the history
among consecutive transactions
  • Loading branch information
hai-rise committed Apr 25, 2024
1 parent 7e3d322 commit dde280e
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 143 deletions.
87 changes: 45 additions & 42 deletions Cargo.lock

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

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

[dependencies]
dashmap = "5.5.3"
revm = "8.0.0"
revm = { git = "https://github.com/risechain/revm", rev = "3c046a6aa734d914dc37bcade8ac5932a9599a23" }

[lints]
rust.missing_debug_implementations = "warn"
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ Finally, while Aptos and Polygon embed their Block-STM implementation directly i

### V1 TODO

- Properly handle the block's beneficiary account, which makes all transactions interdependent when paying gas. We should distinguish beneficiary reads from execution reads (like `address` and `balance` opcodes) so we can defer or atomically update the beneficiary balance. We may pass in a custom `PostExecutionHandler::reward_beneficiary` to solve this.
- Properly check for changed account infos in `revm`'s `ResultAndState` before adding them to the write set.
- Complete a robust version that passes all the relevant [ethereum/tests](https://github.com/ethereum/tests).
- Design more tests for larger blocks with complex state transitions and dependencies (ERC-20 and Uniswap transactions, etc.); contribute upstream if appropriate.
- Provide deep benchmarks, including a [Reth](https://github.com/paradigmxyz/reth) integration for syncing & building blocks.
Expand Down
52 changes: 38 additions & 14 deletions src/block_stm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
mv_memory::MvMemory,
scheduler::Scheduler,
vm::{Vm, VmExecutionResult},
ExecutionTask, Storage, Task, TxVersion, ValidationTask,
ExecutionTask, MemoryLocation, MemoryValue, Storage, Task, TxVersion, ValidationTask,
};

/// An interface to execute Block-STM.
Expand All @@ -30,8 +30,16 @@ impl BlockSTM {
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, txs.clone(), mv_memory.clone());
let execution_results = Mutex::new(vec![None; txs.len()]);
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();
// TODO: Should we move this to `Vm`?
let execution_results = (0..block_size).map(|_| Mutex::new(None)).collect();
// TODO: Better thread handling
thread::scope(|scope| {
for _ in 0..concurrency_level.into() {
Expand Down Expand Up @@ -76,17 +84,33 @@ impl BlockSTM {
}
});

println!("MV Memory snapshot length: {}", mv_memory.snapshot().len());

// TODO: Better error handling
let execution_results = execution_results.lock().unwrap();
// We lazily evaluate the final beneficiary account's balance at the end of each transaction
// to avoid "implicit" dependency among consecutive transactions that read & write there.
execution_results
.iter()
.cloned()
// TODO: Better error handling
// Scheduler shouldn't claim `done` when
// there is still a `None`` result.
.map(|r| r.unwrap())
// TODO: Better error handling. Scheduler shouldn't claim `done` when there is still
// a `None`` result.
.map(|m| m.lock().unwrap().clone().unwrap())
.enumerate()
.map(|(tx_idx, mut result_and_state)| {
// TODO: Support pre-EIP-3651 when the beneficiary account may not be loaded and be
// present in the state changes map.
let beneficiary_account =
result_and_state.state.get_mut(&block_env.coinbase).unwrap();
beneficiary_account.mark_touch();
// TODO: Better error handling & make this much faster
match mv_memory.read_absolute(&MemoryLocation::Basic(block_env.coinbase), tx_idx) {
MemoryValue::Basic(account) => {
beneficiary_account_info = account;
}
MemoryValue::LazyBeneficiaryBalance(addition) => {
beneficiary_account_info.balance += addition;
}
_ => unreachable!(),
}
beneficiary_account.info = beneficiary_account_info.clone();
result_and_state
})
.collect()
}
}
Expand All @@ -102,7 +126,7 @@ fn try_execute(
mv_memory: &Arc<MvMemory>,
vm: &Vm,
scheduler: &Scheduler,
execution_results: &Mutex<Vec<Option<ResultAndState>>>,
execution_results: &Vec<Mutex<Option<ResultAndState>>>,
tx_version: &TxVersion,
) -> Option<ValidationTask> {
match vm.execute(tx_version.tx_idx) {
Expand All @@ -119,7 +143,7 @@ fn try_execute(
read_set,
write_set,
} => {
execution_results.lock().unwrap()[tx_version.tx_idx] = Some(result_and_state);
*execution_results[tx_version.tx_idx].lock().unwrap() = Some(result_and_state);
let wrote_new_location = mv_memory.record(tx_version, read_set, write_set);
scheduler.finish_execution(tx_version, wrote_new_location)
}
Expand Down
20 changes: 15 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

use revm::primitives::{AccountInfo, Address, U256};

// For simplicity, we first stop at the address & storage level. We
// can still break an address into smaller memory locations to
// minimize re-executions on "broad" state conflicts?
// TODO: More granularity here, for instance, to separate an account's
// balance, nonce, etc. instead of marking conflict at the whole account.
// That way we may also generalize beneficiary balance's lazy update
// behaviour into `MemoryValue` for more use cases.
// TODO: It would be nice if we could tie the different cases of
// memory locations & values at the type level, to prevent lots of
// matches & potentially dangerous mismatch mistakes.
Expand All @@ -17,9 +18,18 @@ enum MemoryLocation {
Storage((Address, U256)),
}

#[derive(Clone)]
#[derive(Debug, Clone)]
enum MemoryValue {
Basic(AccountInfo),
// We lazily update the beneficiary balance to avoid continuous
// dependencies as all transactions read and write to it. We
// either evaluate all these beneficiary account states at the
// end of BlockSTM, or when there is an explicit read.
// Important: The value of this lazy (update) balance is the gas
// it receives in the transaction, to be added to the absolute
// balance at the end of the previous transaction.
// We can probably generalize this to `AtomicBalanceAddition`.
LazyBeneficiaryBalance(U256),
Storage(U256),
}

Expand Down Expand Up @@ -47,7 +57,7 @@ struct TxVersion {

// The origin of a memory read. It could be from the live multi-version
// data structure or from storage (chain state before block execution).
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
enum ReadOrigin {
// The previous transaction version that wrote the value.
MvMemory(TxVersion),
Expand Down
Loading

0 comments on commit dde280e

Please sign in to comment.