diff --git a/Cargo.lock b/Cargo.lock index 76cc8b992..07fc8c3f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13236,13 +13236,12 @@ dependencies = [ "bitcoin", "bitcoin-bosd", "borsh", + "const-hex", "digest 0.10.7", "hex", "musig2", "num_enum 0.7.3", "rand", - "reth-primitives", - "revm-primitives", "secp256k1", "serde", "serde_json", diff --git a/bin/prover-client/src/operators/cl_stf.rs b/bin/prover-client/src/operators/cl_stf.rs index 15ae5ac3e..c2ede7033 100644 --- a/bin/prover-client/src/operators/cl_stf.rs +++ b/bin/prover-client/src/operators/cl_stf.rs @@ -72,7 +72,7 @@ impl ClStfOperator { pub async fn get_exec_id(&self, cl_block_id: L2BlockId) -> Result { let header = self.get_l2_block_header(cl_block_id).await?; let block = self.evm_ee_operator.get_block(header.block_idx).await?; - Ok(block.header.hash.into()) + Ok(Buf32(block.header.hash.into())) } /// Retrieves the specified number of ancestor block IDs for the given block ID. diff --git a/bin/prover-client/src/operators/evm_ee.rs b/bin/prover-client/src/operators/evm_ee.rs index 7867c6b63..32f523052 100644 --- a/bin/prover-client/src/operators/evm_ee.rs +++ b/bin/prover-client/src/operators/evm_ee.rs @@ -90,11 +90,12 @@ impl ProvingOp for EvmEeOperator { if blkid == start_block_hash { break; } else { - blkid = Buf32::from( + blkid = Buf32( self.get_block_header(blkid) .await .map_err(|e| ProvingTaskError::RpcError(e.to_string()))? - .parent_hash, + .parent_hash + .into(), ); } } diff --git a/crates/evmexec/src/block.rs b/crates/evmexec/src/block.rs index 128840bb8..f246117f7 100644 --- a/crates/evmexec/src/block.rs +++ b/crates/evmexec/src/block.rs @@ -1,5 +1,5 @@ use borsh::BorshDeserialize; -use revm_primitives::B256; +use revm_primitives::{FixedBytes, B256}; use strata_primitives::evm_exec::EVMExtraPayload; use strata_state::block::{L2Block, L2BlockBundle}; use thiserror::Error; @@ -12,7 +12,7 @@ pub(crate) struct EVML2Block { impl EVML2Block { pub fn block_hash(&self) -> B256 { - self.extra_payload.block_hash().into() + FixedBytes(*self.extra_payload.block_hash().as_ref()) } } diff --git a/crates/evmexec/src/engine.rs b/crates/evmexec/src/engine.rs index 2a6474a24..2c2bc9964 100644 --- a/crates/evmexec/src/engine.rs +++ b/crates/evmexec/src/engine.rs @@ -526,7 +526,7 @@ mod tests { let timestamp = 0; let el_ops = vec![]; - let safe_l1_block = FixedBytes::<32>::random().into(); + let safe_l1_block = Buf32(FixedBytes::<32>::random().into()); let prev_l2_block = Buf32(FixedBytes::<32>::random().into()).into(); let payload_env = PayloadEnv::new(timestamp, prev_l2_block, safe_l1_block, el_ops); @@ -570,7 +570,7 @@ mod tests { let fcs = ForkchoiceState::default(); let el_payload = ElPayload { - base_fee_per_gas: FixedBytes::<32>::from(U256::from(10)).into(), + base_fee_per_gas: Buf32(FixedBytes::<32>::from(U256::from(10)).into()), parent_hash: Default::default(), fee_recipient: Default::default(), state_root: Default::default(), diff --git a/crates/evmexec/src/fork_choice_state.rs b/crates/evmexec/src/fork_choice_state.rs index f1c98e349..1233cb3d3 100644 --- a/crates/evmexec/src/fork_choice_state.rs +++ b/crates/evmexec/src/fork_choice_state.rs @@ -25,7 +25,9 @@ pub fn fork_choice_state_initial( .and_then(|state| state.sync()) .map(|sync_state| sync_state.chain_tip_blkid()), )? - .unwrap_or(rollup_params.evm_genesis_block_hash.into()); + .unwrap_or(revm_primitives::FixedBytes( + *rollup_params.evm_genesis_block_hash.as_ref(), + )); let finalized_block_hash = get_block_hash_by_id( db.as_ref(), diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index af84b88e5..8db302a0a 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -15,13 +15,12 @@ bitcoin-bosd = { workspace = true, features = [ "arbitrary", ] } borsh.workspace = true +const-hex = "1.14" digest.workspace = true hex.workspace = true musig2 = { workspace = true, features = ["serde"] } num_enum.workspace = true rand = { workspace = true, optional = true } -reth-primitives.workspace = true -revm-primitives.workspace = true secp256k1 = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/primitives/src/buf.rs b/crates/primitives/src/buf.rs index 47a68b2d6..5ab601431 100644 --- a/crates/primitives/src/buf.rs +++ b/crates/primitives/src/buf.rs @@ -5,7 +5,7 @@ use bitcoin::{ secp256k1::{schnorr, SecretKey, XOnlyPublicKey}, BlockHash, Txid, Wtxid, }; -use revm_primitives::alloy_primitives::hex; +use const_hex as hex; #[cfg(feature = "zeroize")] use zeroize::Zeroize; @@ -237,7 +237,7 @@ mod tests { fn test_buf32_serialization() { assert_eq!( serde_json::to_string(&Buf32::from([0; 32])).unwrap(), - String::from("\"0x0000000000000000000000000000000000000000000000000000000000000000\"") + String::from("\"0000000000000000000000000000000000000000000000000000000000000000\"") ); assert_eq!( @@ -246,7 +246,7 @@ mod tests { 1, 1, 1, 170u8 ])) .unwrap(), - String::from("\"0x01010101010101010101010101010101010101010101010101010101010101aa\"") + String::from("\"01010101010101010101010101010101010101010101010101010101010101aa\"") ); } @@ -263,4 +263,20 @@ mod tests { assert_eq!(buf32, Buf32::from([0; 32])); assert_eq!(buf64, Buf64::from([0; 64])); } + + #[test] + fn test_buf32_parse() { + "0x37ad61cff1367467a98cf7c54c4ac99e989f1fbb1bc1e646235e90c065c565ba" + .parse::() + .unwrap(); + } + + #[test] + fn test_buf32_from_str() { + Buf32::from_str("a9f913c3d7fe56c462228ad22bb7631742a121a6a138d57c1fc4a351314948fa") + .unwrap(); + + Buf32::from_str("81060cb3997dcefc463e3db0a776efb5360e458064a666459b8807f60c0201c2") + .unwrap(); + } } diff --git a/crates/primitives/src/l1/btc.rs b/crates/primitives/src/l1/btc.rs index 0f35166a2..4b59139d1 100644 --- a/crates/primitives/src/l1/btc.rs +++ b/crates/primitives/src/l1/btc.rs @@ -21,7 +21,6 @@ use bitcoin::{ use bitcoin_bosd::Descriptor; use borsh::{BorshDeserialize, BorshSerialize}; use rand::rngs::OsRng; -use revm_primitives::FixedBytes; use serde::{de, Deserialize, Deserializer, Serialize}; use crate::{buf::Buf32, constants::HASH_SIZE, errors::ParseError}; @@ -736,9 +735,7 @@ impl XOnlyPk { let pubkey_bytes = &script_pubkey.as_bytes()[2..34]; let output_key: XOnlyPublicKey = XOnlyPublicKey::from_slice(pubkey_bytes)?; - let serialized_key: FixedBytes<32> = output_key.serialize().into(); - - Ok(Self(Buf32(serialized_key.into()))) + Ok(Self(Buf32(output_key.serialize()))) } else { Err(ParseError::UnsupportedAddress(checked_addr.address_type())) } diff --git a/crates/primitives/src/macros.rs b/crates/primitives/src/macros.rs index 49ee2c69d..fa46df088 100644 --- a/crates/primitives/src/macros.rs +++ b/crates/primitives/src/macros.rs @@ -110,22 +110,6 @@ pub mod internal { } } - impl ::std::convert::From<$name> - for ::revm_primitives::alloy_primitives::FixedBytes<$len> - { - fn from(value: $name) -> Self { - value.0.into() - } - } - - impl ::std::convert::From<::revm_primitives::alloy_primitives::FixedBytes<$len>> - for $name - { - fn from(value: ::revm_primitives::alloy_primitives::FixedBytes<$len>) -> Self { - value.0.into() - } - } - impl ::std::default::Default for $name { fn default() -> Self { Self([0; $len]) @@ -183,31 +167,115 @@ pub mod internal { } macro_rules! impl_buf_serde { - // Historically, the Buf* types were wrapping FixedBytes. - // Delegate serde to FixedBytes for now to not break anything. - // TODO (STR-453): rework serde. ($name:ident, $len:expr) => { impl ::serde::Serialize for $name { - #[inline] - fn serialize( - &self, - serializer: S, - ) -> Result { - ::serde::Serialize::serialize( - &::revm_primitives::alloy_primitives::FixedBytes::<$len>::from(&self.0), - serializer, - ) + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + // Convert the inner array to a hex string (without 0x prefix) + let hex_str = ::hex::encode(&self.0); + serializer.serialize_str(&hex_str) } } impl<'de> ::serde::Deserialize<'de> for $name { - #[inline] - fn deserialize>( - deserializer: D, - ) -> Result { - ::serde::Deserialize::deserialize(deserializer).map( - |v: ::revm_primitives::alloy_primitives::FixedBytes<$len>| Self::from(v), - ) + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + // Define a Visitor for deserialization. + // P.S. Make it in the scope of the function to avoid name conflicts + // for different macro_rules invocations. + struct BufVisitor; + + impl<'de> ::serde::de::Visitor<'de> for BufVisitor { + type Value = $name; + + fn expecting( + &self, + formatter: &mut ::std::fmt::Formatter, + ) -> ::std::fmt::Result { + write!( + formatter, + "a hex string with an optional 0x prefix representing {} bytes", + $len + ) + } + + fn visit_str(self, v: &str) -> Result<$name, E> + where + E: ::serde::de::Error, + { + // Remove the optional "0x" or "0X" prefix if present. + let hex_str = if v.starts_with("0x") || v.starts_with("0X") { + &v[2..] + } else { + v + }; + + // Decode the hex string into a vector of bytes. + let bytes = ::hex::decode(hex_str).map_err(E::custom)?; + + // Ensure the decoded bytes have the expected length. + if bytes.len() != $len { + return Err(E::custom(format!( + "expected {} bytes, got {}", + $len, + bytes.len() + ))); + } + + // Convert the Vec into a fixed-size array. + let mut array = [0u8; $len]; + array.copy_from_slice(&bytes); + Ok($name(array)) + } + + fn visit_bytes(self, v: &[u8]) -> Result<$name, E> + where + E: ::serde::de::Error, + { + if v.len() == $len { + let mut array = [0u8; $len]; + array.copy_from_slice(v); + Ok($name(array)) + } else { + // Try to interpret the bytes as a UTF-8 encoded hex string. + let s = ::std::str::from_utf8(v).map_err(E::custom)?; + self.visit_str(s) + } + } + + fn visit_seq(self, mut seq: A) -> Result<$name, A::Error> + where + A: ::serde::de::SeqAccess<'de>, + { + let mut array = [0u8; $len]; + for i in 0..$len { + array[i] = seq + .next_element::()? + .ok_or_else(|| ::serde::de::Error::invalid_length(i, &self))?; + } + // Ensure there are no extra elements. + if let Some(_) = seq.next_element::()? { + return Err(::serde::de::Error::custom(format!( + "expected a sequence of exactly {} bytes, but found extra elements", + $len + ))); + } + Ok($name(array)) + } + } + + if deserializer.is_human_readable() { + // For human-readable formats, support multiple input types. + // Use with the _any, so serde can decide whether to visit seq, bytes or str. + deserializer.deserialize_any(BufVisitor) + } else { + // Bincode does not support DeserializeAny, so deserializing with the _str. + deserializer.deserialize_str(BufVisitor) + } } } }; @@ -219,8 +287,12 @@ pub mod internal { #[cfg(test)] mod tests { + + #[derive(PartialEq)] pub struct TestBuf20([u8; 20]); + crate::macros::internal::impl_buf_common!(TestBuf20, 20); + crate::macros::internal::impl_buf_serde!(TestBuf20, 20); #[test] fn test_from_into_array() { @@ -241,4 +313,63 @@ mod tests { let buf = TestBuf20::default(); assert_eq!(buf.as_slice(), &[0; 20]); } + + #[test] + fn test_serialize_hex() { + let data = [1u8; 20]; + let buf = TestBuf20(data); + let json = serde_json::to_string(&buf).unwrap(); + // Since we serialize as a string, json should be the hex-encoded string wrapped in quotes. + let expected = format!("\"{}\"", hex::encode(data)); + assert_eq!(json, expected); + } + + #[test] + fn test_deserialize_hex_without_prefix() { + let data = [2u8; 20]; + let hex_str = hex::encode(data); + let json = format!("\"{}\"", hex_str); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_hex_with_prefix() { + let data = [3u8; 20]; + let hex_str = hex::encode(data); + let json = format!("\"0x{}\"", hex_str); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_from_seq() { + // Provide a JSON array of numbers. + let data = [5u8; 20]; + let json = serde_json::to_string(&data).unwrap(); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_deserialize_from_bytes_via_array() { + // Although JSON doesn't have a native "bytes" type, this test uses a JSON array + // to exercise the same code path as visit_bytes when deserializing a sequence. + let data = [7u8; 20]; + // Simulate input as a JSON array + let json = serde_json::to_string(&data).unwrap(); + let buf: TestBuf20 = serde_json::from_str(&json).unwrap(); + assert_eq!(buf, TestBuf20(data)); + } + + #[test] + fn test_bincode_roundtrip() { + let data = [9u8; 20]; + let buf = TestBuf20(data); + // bincode is non-human-readable so our implementation will use deserialize_tuple. + let encoded = bincode::serialize(&buf).expect("bincode serialization failed"); + let decoded: TestBuf20 = + bincode::deserialize(&encoded).expect("bincode deserialization failed"); + assert_eq!(buf, decoded); + } } diff --git a/crates/proof-impl/evm-ee-stf/src/utils.rs b/crates/proof-impl/evm-ee-stf/src/utils.rs index 82b2cb360..a3f131934 100644 --- a/crates/proof-impl/evm-ee-stf/src/utils.rs +++ b/crates/proof-impl/evm-ee-stf/src/utils.rs @@ -40,7 +40,7 @@ pub fn generate_exec_update(el_proof_pp: &EvmBlockStfOutput) -> ExecSegment { create_evm_extra_payload(Buf32(*el_proof_pp.new_blockhash)), ); - let update_output = UpdateOutput::new_from_state(el_proof_pp.new_state_root.into()) + let update_output = UpdateOutput::new_from_state((*el_proof_pp.new_state_root).into()) .with_withdrawals(withdrawals); let exec_update = ExecUpdate::new(update_input, update_output); diff --git a/functional-tests/utils/utils.py b/functional-tests/utils/utils.py index 7ee7fc314..ae051e723 100644 --- a/functional-tests/utils/utils.py +++ b/functional-tests/utils/utils.py @@ -442,7 +442,7 @@ def get_bridge_pubkey_from_cfg(cfg_params) -> str: Get the bridge pubkey from the config. """ # Slight hack to convert to appropriate operator pubkey from cfg values. - op_pks = ["02" + pk[2:] for pk in cfg_params.operator_config.get_operators_pubkeys()] + op_pks = ["02" + pk for pk in cfg_params.operator_config.get_operators_pubkeys()] op_x_only_pks = [convert_to_xonly_pk(pk) for pk in op_pks] agg_pubkey = musig_aggregate_pks(op_x_only_pks) return agg_pubkey