From 41d8a86df4889ae6c1619c93fcaf3a8d9f997c10 Mon Sep 17 00:00:00 2001 From: Renato Dinhani <101204870+dinhani-cw@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:51:14 -0300 Subject: [PATCH] feat: conflict detection between executions (#690) --- src/eth/primitives/execution.rs | 49 +++++++++++++++++++ src/eth/primitives/execution_conflict.rs | 18 +++---- .../postgres_permanent/postgres_permanent.rs | 10 +++- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/eth/primitives/execution.rs b/src/eth/primitives/execution.rs index 1194aa140..eb8cc40b4 100644 --- a/src/eth/primitives/execution.rs +++ b/src/eth/primitives/execution.rs @@ -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; @@ -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 { + let mut conflicts = ExecutionConflictsBuilder::default(); + + for current in self.changes.values() { + let Some(next) = next_execution.changes.get(¤t.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 ¤t.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 diff --git a/src/eth/primitives/execution_conflict.rs b/src/eth/primitives/execution_conflict.rs index 13503008b..045e948a0 100644 --- a/src/eth/primitives/execution_conflict.rs +++ b/src/eth/primitives/execution_conflict.rs @@ -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 { @@ -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 }, } diff --git a/src/eth/storage/postgres_permanent/postgres_permanent.rs b/src/eth/storage/postgres_permanent/postgres_permanent.rs index 88b1d65a2..1552a40e2 100644 --- a/src/eth/storage/postgres_permanent/postgres_permanent.rs +++ b/src/eth/storage/postgres_permanent/postgres_permanent.rs @@ -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); }