From d70a0f25823bc0c8337e40abb9861575acf8b84f Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 2 Apr 2026 20:16:33 +0000 Subject: [PATCH 1/2] fix(kona): use BTreeMap for deterministic JSON serialization in interop host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace HashMap with BTreeMap for DependencySet.dependencies and read_rollup_configs return type. HashMap iteration order is randomized per-process in Rust, so when kona-host serializes these maps as JSON preimages for the cannon VM, two separate kona-host processes (test helper vs challenger) can produce different byte sequences for the same data. This causes cannon VM state divergence, making the challenger disagree with the test's claims and step at the wrong trace index — without uploading the required preimage. The fix ensures deterministic key ordering via BTreeMap, so all kona-host processes produce identical JSON serialization regardless of process-level hash seeds. Fixes ethereum-optimism/optimism#19892 Co-Authored-By: Claude Opus 4.6 (1M context) --- rust/kona/bin/host/src/interop/cfg.rs | 9 +++++---- rust/kona/crates/protocol/interop/src/depset.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/rust/kona/bin/host/src/interop/cfg.rs b/rust/kona/bin/host/src/interop/cfg.rs index 6338521c11446..93274b212c41b 100644 --- a/rust/kona/bin/host/src/interop/cfg.rs +++ b/rust/kona/bin/host/src/interop/cfg.rs @@ -22,7 +22,7 @@ use kona_providers_alloy::{OnlineBeaconClient, OnlineBlobProvider}; use kona_std_fpvm::{FileChannel, FileDescriptor}; use op_alloy_network::Optimism; use serde::Serialize; -use std::{collections::HashMap, path::PathBuf, str::FromStr, sync::Arc}; +use std::{collections::{BTreeMap, HashMap}, path::PathBuf, str::FromStr, sync::Arc}; use tokio::task::{self, JoinHandle}; /// The interop host application. @@ -228,13 +228,14 @@ impl InteropHost { } /// Reads the [`RollupConfig`]s from the file system and returns a map of L2 chain ID -> - /// [`RollupConfig`]s. + /// [`RollupConfig`]s. Uses `BTreeMap` to ensure deterministic JSON serialization order when + /// these configs are served as preimages to the cannon VM. pub fn read_rollup_configs( &self, - ) -> Option, InteropHostError>> { + ) -> Option, InteropHostError>> { let rollup_config_paths = self.rollup_config_paths.as_ref()?; - Some(rollup_config_paths.iter().try_fold(HashMap::default(), |mut acc, path| { + Some(rollup_config_paths.iter().try_fold(BTreeMap::default(), |mut acc, path| { // Read the serialized config from the file system. let ser_config = std::fs::read_to_string(path)?; diff --git a/rust/kona/crates/protocol/interop/src/depset.rs b/rust/kona/crates/protocol/interop/src/depset.rs index 5b9b29e7efc06..ec0f4066fb3e7 100644 --- a/rust/kona/crates/protocol/interop/src/depset.rs +++ b/rust/kona/crates/protocol/interop/src/depset.rs @@ -1,6 +1,6 @@ +use alloc::collections::BTreeMap; use crate::MESSAGE_EXPIRY_WINDOW; use alloy_primitives::ChainId; -use kona_registry::HashMap; /// Configuration for a dependency of a chain #[derive(Debug, Clone, PartialEq, Eq)] @@ -15,7 +15,7 @@ pub struct ChainDependency {} #[allow(clippy::zero_sized_map_values)] pub struct DependencySet { /// Dependencies information per chain. - pub dependencies: HashMap, + pub dependencies: BTreeMap, /// Override message expiry window to use for this dependency set. pub override_message_expiry_window: Option, @@ -35,11 +35,11 @@ impl DependencySet { #[allow(clippy::zero_sized_map_values)] mod tests { use super::*; + use alloc::collections::BTreeMap; use alloy_primitives::ChainId; - use kona_registry::HashMap; const fn create_dependency_set( - dependencies: HashMap, + dependencies: BTreeMap, override_expiry: u64, ) -> DependencySet { DependencySet { dependencies, override_message_expiry_window: Some(override_expiry) } @@ -47,7 +47,7 @@ mod tests { #[test] fn test_get_message_expiry_window_default() { - let deps = HashMap::default(); + let deps = BTreeMap::default(); // override_message_expiry_window is 0, so default should be used let ds = create_dependency_set(deps, 0); assert_eq!( @@ -59,7 +59,7 @@ mod tests { #[test] fn test_get_message_expiry_window_override() { - let deps = HashMap::default(); + let deps = BTreeMap::default(); let override_value = 12345; let ds = create_dependency_set(deps, override_value); assert_eq!( From 457682b62c1086f462a81d72f187f900b3f15e8d Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Thu, 2 Apr 2026 20:22:05 +0000 Subject: [PATCH 2/2] fix(kona): fix rustfmt for BTreeMap imports Co-Authored-By: Claude Opus 4.6 (1M context) --- rust/kona/bin/host/src/interop/cfg.rs | 7 ++++++- rust/kona/crates/protocol/interop/src/depset.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/kona/bin/host/src/interop/cfg.rs b/rust/kona/bin/host/src/interop/cfg.rs index 93274b212c41b..a178fb774886c 100644 --- a/rust/kona/bin/host/src/interop/cfg.rs +++ b/rust/kona/bin/host/src/interop/cfg.rs @@ -22,7 +22,12 @@ use kona_providers_alloy::{OnlineBeaconClient, OnlineBlobProvider}; use kona_std_fpvm::{FileChannel, FileDescriptor}; use op_alloy_network::Optimism; use serde::Serialize; -use std::{collections::{BTreeMap, HashMap}, path::PathBuf, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + path::PathBuf, + str::FromStr, + sync::Arc, +}; use tokio::task::{self, JoinHandle}; /// The interop host application. diff --git a/rust/kona/crates/protocol/interop/src/depset.rs b/rust/kona/crates/protocol/interop/src/depset.rs index ec0f4066fb3e7..eb99ec204d80a 100644 --- a/rust/kona/crates/protocol/interop/src/depset.rs +++ b/rust/kona/crates/protocol/interop/src/depset.rs @@ -1,5 +1,5 @@ -use alloc::collections::BTreeMap; use crate::MESSAGE_EXPIRY_WINDOW; +use alloc::collections::BTreeMap; use alloy_primitives::ChainId; /// Configuration for a dependency of a chain