Repository: bluealloy/revm (language: rust)

Issue:
Manually impl `PartialEq` for `Reverts`
In reth's invalid block hooks, we first compare the invalid block's `BundleState` to a re-executed version, then use `serde_json` to serialize `BundleState` and save it as JSON. Because the reverts are unsorted, this leads to equivalent `BundleState`s being seen as not equal. Instead, there should be a comparison API for `Reverts` that returns `true` if the reverts have the same contents, regardless of ordering.


Discussion / hints:
@Rjected like this?

```rust
    /// Compares two `Reverts` for equality, sorting the internal `Vec`s in place.
    pub fn unordered_eq(&mut self, other: &mut Self) -> bool {
        // Sort inner vectors by Address in place
        self.sort();
        other.sort();

        // Compare sorted structures for equality
        self.0 == other.0
    }
 ```
can I take this? @Rjected 

Fix patch:
diff --git a/crates/bytecode/src/bytecode.rs b/crates/bytecode/src/bytecode.rs
index f1a4a58ae2..d7fa73b46e 100644
--- a/crates/bytecode/src/bytecode.rs
+++ b/crates/bytecode/src/bytecode.rs
@@ -8,7 +8,7 @@ use primitives::{keccak256, Address, Bytes, B256, KECCAK_EMPTY};
 use std::sync::Arc;
 
 /// State of the [`Bytecode`] analysis.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub enum Bytecode {
     /// No analysis has been performed.
diff --git a/crates/bytecode/src/eip7702.rs b/crates/bytecode/src/eip7702.rs
index 191a0c33da..88a1ca5319 100644
--- a/crates/bytecode/src/eip7702.rs
+++ b/crates/bytecode/src/eip7702.rs
@@ -14,7 +14,7 @@ pub const EIP7702_VERSION: u8 = 0;
 ///
 /// Format of EIP-7702 bytecode consist of:
 /// 0xEF00 (MAGIC) + 0x00 (VERSION) + 20 bytes of address.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct Eip7702Bytecode {
     pub delegated_address: Address,
diff --git a/crates/bytecode/src/eof.rs b/crates/bytecode/src/eof.rs
index 6aa7a00597..ce9279b162 100644
--- a/crates/bytecode/src/eof.rs
+++ b/crates/bytecode/src/eof.rs
@@ -27,7 +27,7 @@ pub static EOF_MAGIC_BYTES: Bytes = bytes!("ef00");
 /// EVM Object Format (EOF) container.
 ///
 /// It consists of a header, body and the raw original bytes.
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct Eof {
     pub header: EofHeader,
diff --git a/crates/bytecode/src/eof/body.rs b/crates/bytecode/src/eof/body.rs
index 66ccd9707c..9b0423820b 100644
--- a/crates/bytecode/src/eof/body.rs
+++ b/crates/bytecode/src/eof/body.rs
@@ -7,7 +7,7 @@ use std::vec::Vec;
 /// Contains types, code, container and data sections.
 ///
 /// Can be used to create a new EOF container using the [`into_eof`](EofBody::into_eof) method.
-#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct EofBody {
     pub types_section: Vec<TypesSection>,
diff --git a/crates/bytecode/src/eof/header.rs b/crates/bytecode/src/eof/header.rs
index a1226678cf..bbe3e364c0 100644
--- a/crates/bytecode/src/eof/header.rs
+++ b/crates/bytecode/src/eof/header.rs
@@ -5,7 +5,7 @@ use super::{
 use std::vec::Vec;
 
 /// EOF Header containing
-#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct EofHeader {
     /// Size of EOF types section.
diff --git a/crates/bytecode/src/eof/types_section.rs b/crates/bytecode/src/eof/types_section.rs
index c2c6c475db..87f828321c 100644
--- a/crates/bytecode/src/eof/types_section.rs
+++ b/crates/bytecode/src/eof/types_section.rs
@@ -8,7 +8,7 @@ use std::vec::Vec;
 const EOF_NON_RETURNING_FUNCTION: u8 = 0x80;
 
 /// Types section that contains stack information for matching code section.
-#[derive(Debug, Clone, Default, Hash, PartialEq, Eq, Copy)]
+#[derive(Debug, Clone, Default, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct TypesSection {
     /// inputs - 1 byte - `0x00-0x7F`
diff --git a/crates/bytecode/src/legacy/analyzed.rs b/crates/bytecode/src/legacy/analyzed.rs
index 1a8f08511c..e16dfc8a64 100644
--- a/crates/bytecode/src/legacy/analyzed.rs
+++ b/crates/bytecode/src/legacy/analyzed.rs
@@ -4,7 +4,7 @@ use primitives::Bytes;
 use std::sync::Arc;
 
 // Legacy analyzed
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct LegacyAnalyzedBytecode {
     /// Bytecode with 32 zero bytes padding.
diff --git a/crates/bytecode/src/legacy/jump_map.rs b/crates/bytecode/src/legacy/jump_map.rs
index bd791646fa..32069c0f0c 100644
--- a/crates/bytecode/src/legacy/jump_map.rs
+++ b/crates/bytecode/src/legacy/jump_map.rs
@@ -3,7 +3,7 @@ use primitives::hex;
 use std::{fmt::Debug, sync::Arc};
 
 /// A map of valid `jump` destinations.
-#[derive(Clone, Default, PartialEq, Eq, Hash)]
+#[derive(Clone, Default, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct JumpTable(pub Arc<BitVec<u8>>);
 
diff --git a/crates/bytecode/src/legacy/raw.rs b/crates/bytecode/src/legacy/raw.rs
index c12252cb2c..b55ea2845a 100644
--- a/crates/bytecode/src/legacy/raw.rs
+++ b/crates/bytecode/src/legacy/raw.rs
@@ -5,7 +5,7 @@ use core::ops::Deref;
 use primitives::Bytes;
 use std::{sync::Arc, vec::Vec};
 
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct LegacyRawBytecode(pub Bytes);
 
diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs
index 6f0d8e7f78..4de97c8824 100644
--- a/crates/database/src/states/account_status.rs
+++ b/crates/database/src/states/account_status.rs
@@ -14,7 +14,7 @@
 /// - `Destroyed`: the account has been destroyed.
 /// - `DestroyedChanged`: the account has been destroyed and then modified.
 /// - `DestroyedAgain`: the account has been destroyed again.
-#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub enum AccountStatus {
     #[default]
diff --git a/crates/database/src/states/reverts.rs b/crates/database/src/states/reverts.rs
index 3c14154cdc..259739259e 100644
--- a/crates/database/src/states/reverts.rs
+++ b/crates/database/src/states/reverts.rs
@@ -4,11 +4,13 @@ use super::{
 };
 use core::ops::{Deref, DerefMut};
 use primitives::{Address, HashMap, U256};
+use std::cmp::Ordering;
+
 use state::AccountInfo;
 use std::vec::Vec;
 
 /// Contains reverts of multiple account in multiple transitions (Transitions as a block).
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Debug, Default, Eq)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct Reverts(Vec<Vec<(Address, AccountRevert)>>);
 
@@ -81,6 +83,36 @@ impl Reverts {
         state_reverts
     }
 
+    /// Compare two Reverts instances, ignoring the order of elements
+    pub fn content_eq(&self, other: &Self) -> bool {
+        if self.0.len() != other.0.len() {
+            return false;
+        }
+
+        for (self_transition, other_transition) in self.0.iter().zip(other.0.iter()) {
+            if self_transition.len() != other_transition.len() {
+                return false;
+            }
+
+            let mut self_transition = self_transition.clone();
+            let mut other_transition = other_transition.clone();
+            // Sort both transitions
+            self_transition.sort_by(|(addr1, revert1), (addr2, revert2)| {
+                addr1.cmp(addr2).then_with(|| revert1.cmp(revert2))
+            });
+            other_transition.sort_by(|(addr1, revert1), (addr2, revert2)| {
+                addr1.cmp(addr2).then_with(|| revert1.cmp(revert2))
+            });
+
+            // Compare sorted transitions
+            if self_transition != other_transition {
+                return false;
+            }
+        }
+
+        true
+    }
+
     /// Consume reverts and create [`PlainStateReverts`].
     ///
     /// Note that account are sorted by address.
@@ -90,6 +122,12 @@ impl Reverts {
     }
 }
 
+impl PartialEq for Reverts {
+    fn eq(&self, other: &Self) -> bool {
+        self.content_eq(other)
+    }
+}
+
 /// Assumption is that Revert can return full state from any future state to any past state.
 ///
 /// It is created when new account state is applied to old account state.
@@ -198,9 +236,55 @@ impl AccountRevert {
     }
 }
 
+/// Implements partial ordering for AccountRevert
+impl PartialOrd for AccountRevert {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+/// Implements total ordering for AccountRevert
+impl Ord for AccountRevert {
+    fn cmp(&self, other: &Self) -> Ordering {
+        // First compare accounts
+        if let Some(ord) = self.account.partial_cmp(&other.account) {
+            if ord != Ordering::Equal {
+                return ord;
+            }
+        }
+
+        // Convert HashMaps to sorted vectors for comparison
+        let mut self_storage: Vec<_> = self.storage.iter().collect();
+        let mut other_storage: Vec<_> = other.storage.iter().collect();
+
+        // Sort by key and then by value
+        self_storage.sort_by(|(k1, v1), (k2, v2)| k1.cmp(k2).then_with(|| v1.cmp(v2)));
+        other_storage.sort_by(|(k1, v1), (k2, v2)| k1.cmp(k2).then_with(|| v1.cmp(v2)));
+
+        // Compare each element
+        for (self_entry, other_entry) in self_storage.iter().zip(other_storage.iter()) {
+            let key_ord = self_entry.0.cmp(other_entry.0);
+            if key_ord != Ordering::Equal {
+                return key_ord;
+            }
+            let value_ord = self_entry.1.cmp(other_entry.1);
+            if value_ord != Ordering::Equal {
+                return value_ord;
+            }
+        }
+
+        // If one vector is longer than the other, or if all elements are equal
+        self_storage
+            .len()
+            .cmp(&other_storage.len())
+            .then_with(|| self.previous_status.cmp(&other.previous_status))
+            .then_with(|| self.wipe_storage.cmp(&other.wipe_storage))
+    }
+}
+
 /// Depending on previous state of account info this
 /// will tell us what to do on revert.
-#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
+#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub enum AccountInfoRevert {
     #[default]
@@ -219,7 +303,7 @@ pub enum AccountInfoRevert {
 ///
 /// Note: It is completely different state if Storage is Zero or Some or if Storage was
 /// Destroyed. Because if it is destroyed, previous values can be found in database or it can be zero.
-#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
+#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub enum RevertToSlot {
     Some(U256),
diff --git a/crates/state/src/account_info.rs b/crates/state/src/account_info.rs
index 0dbdd75c63..18c2824415 100644
--- a/crates/state/src/account_info.rs
+++ b/crates/state/src/account_info.rs
@@ -3,7 +3,7 @@ use core::hash::{Hash, Hasher};
 use primitives::{B256, KECCAK_EMPTY, U256};
 
 /// AccountInfo account information.
-#[derive(Clone, Debug, Eq)]
+#[derive(Clone, Debug, Eq, Ord, PartialOrd)]
 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
 pub struct AccountInfo {
     /// Account balance.


Test patch: