Skip to content

Commit

Permalink
feat: conflict detection between executions (#690)
Browse files Browse the repository at this point in the history
  • Loading branch information
dinhani-cw authored Apr 24, 2024
1 parent ec69bff commit 41d8a86
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 14 deletions.
49 changes: 49 additions & 0 deletions src/eth/primitives/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::eth::primitives::Account;
use crate::eth::primitives::Address;
use crate::eth::primitives::Bytes;
use crate::eth::primitives::ExecutionAccountChanges;
use crate::eth::primitives::ExecutionConflicts;
use crate::eth::primitives::ExecutionConflictsBuilder;
use crate::eth::primitives::ExecutionResult;
use crate::eth::primitives::ExternalBlock;
use crate::eth::primitives::ExternalReceipt;
Expand Down Expand Up @@ -104,6 +106,53 @@ impl Execution {
None
}

/// Checks conflicts between two executions.
///
/// Assumes self is the present execution and next should happen after self in a serialized context.
pub fn check_conflicts(&self, next_execution: &Execution) -> Option<ExecutionConflicts> {
let mut conflicts = ExecutionConflictsBuilder::default();

for current in self.changes.values() {
let Some(next) = next_execution.changes.get(&current.address) else {
continue;
};

// nonce conflict
let current_nonce = current.nonce.take_modified_ref();
let next_nonce = next.nonce.take_original_ref();
match (current_nonce, next_nonce) {
(Some(current_nonce), Some(next_nonce)) if current_nonce != next_nonce => {
conflicts.add_nonce(current.address, *current_nonce, *next_nonce);
}
_ => {}
}

// balance conflict
let current_balance = current.balance.take_modified_ref();
let next_balance = next.balance.take_original_ref();
match (current_balance, next_balance) {
(Some(current_balance), Some(next_balance)) if current_balance != next_balance => {
conflicts.add_balance(current.address, *current_balance, *next_balance);
}
_ => {}
}

// slot conflicts
for (slot_index, current_slot_change) in &current.slots {
let current_slot = current_slot_change.take_modified_ref();
let next_slot = next.slots.get(slot_index).and_then(|slot| slot.take_original_ref());
match (current_slot, next_slot) {
(Some(current_slot), Some(next_slot)) if current_slot != next_slot => {
conflicts.add_slot(current.address, *slot_index, current_slot.value, next_slot.value);
}
_ => {}
}
}
}

conflicts.build()
}

/// Checks if current execution state matches the information present in the external receipt.
pub fn compare_with_receipt(&self, receipt: &ExternalReceipt) -> anyhow::Result<()> {
// compare execution status
Expand Down
18 changes: 6 additions & 12 deletions src/eth/primitives/execution_conflict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,10 @@ impl ExecutionConflictsBuilder {
#[derive(Debug)]
pub enum ExecutionConflict {
/// Account nonce mismatch.
Nonce {
address: Address,
expected: Nonce,
actual: Nonce,
},
Nonce { address: Address, expected: Nonce, actual: Nonce },

/// Account balance mismatch.
Balance {
address: Address,
expected: Wei,
actual: Wei,
},
Balance { address: Address, expected: Wei, actual: Wei },

/// Slot value mismatch.
Slot {
Expand All @@ -67,7 +59,9 @@ pub enum ExecutionConflict {
actual: SlotValue,
},

Account,
/// Number of modified accounts mismatch.
AccountModifiedCount { expected: usize, actual: usize },

PgSlot,
/// Number of modified slots mismatch.
SlotModifiedCount { expected: usize, actual: usize },
}
10 changes: 8 additions & 2 deletions src/eth/storage/postgres_permanent/postgres_permanent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,19 @@ impl PermanentStorage for PostgresPermanentStorage {

if modified_accounts != expected_modified_accounts {
tx.rollback().await.context("failed to rollback transaction")?;
let error: StorageError = StorageError::Conflict(ExecutionConflicts(nonempty![ExecutionConflict::Account]));
let error: StorageError = StorageError::Conflict(ExecutionConflicts(nonempty![ExecutionConflict::AccountModifiedCount {
expected: expected_modified_accounts,
actual: modified_accounts,
}]));
return Err(error);
}

if modified_slots != expected_modified_slots {
tx.rollback().await.context("failed to rollback transaction")?;
let error: StorageError = StorageError::Conflict(ExecutionConflicts(nonempty![ExecutionConflict::PgSlot]));
let error: StorageError = StorageError::Conflict(ExecutionConflicts(nonempty![ExecutionConflict::SlotModifiedCount {
expected: expected_modified_slots,
actual: modified_slots
}]));
return Err(error);
}

Expand Down

0 comments on commit 41d8a86

Please sign in to comment.