From 733218ac79dddca277cb35e20db20afc870c6d10 Mon Sep 17 00:00:00 2001 From: Gonza Montiel Date: Mon, 15 Dec 2025 14:11:08 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=9B=A1=EF=B8=8F=20=20Check=20origi?= =?UTF-8?q?n=20for=20validator=20set=20messages=20(#343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context The function `v2_sendMessage()` on Snowbridge Gateway contract is **permissionless** (I'm shocked this is the design choice). Any EOA/contract on Ethereum can build a message and send it through our DH bridge. While we don't change our Snowbridge fork, then this will continue to be the case. ### Problem We use `v2_sendMessage()` to send **permissioned** operations to our chain. For instance: update our validator set message (coming next, _slashing-related_ messages). So we do need to restrict the processing of the incoming messages on the Substrate side. ### Fix - I've added a check to `EigenLayerMessageProcessor` that enforces `message.origin` to be only a configured `AuthorisedOrigin`. - I've added an `AuthorisedOrigin` to `pallet_external_validators::Config` - I've configured the `AuthorisedOrigin` to be `DatahavenServiceManagerAddress` in all three runtimes ### Stages - [x] Implementation - [x] Runtime integration tests - [x] Collect `DatahavenServiceManagerAddress` parameter for e2e tests to work Fixes https://github.com/datahaven-xyz/sr-datahaven/issues/12 --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> --- contracts/config/anvil.json | 14 +-- .../pallets/external-validators/src/lib.rs | 5 + .../pallets/external-validators/src/mock.rs | 5 +- operator/primitives/bridge/src/lib.rs | 7 +- operator/runtime/mainnet/src/configs/mod.rs | 100 ++++++++++++++++- .../mainnet/src/configs/runtime_params.rs | 5 +- operator/runtime/stagenet/src/configs/mod.rs | 101 +++++++++++++++++- .../stagenet/src/configs/runtime_params.rs | 5 +- operator/runtime/testnet/src/configs/mod.rs | 99 ++++++++++++++++- .../testnet/src/configs/runtime_params.rs | 5 +- test/.papi/descriptors/package.json | 2 +- test/.papi/metadata/datahaven.scale | Bin 623789 -> 623982 bytes .../parameters/datahaven-parameters.json | 4 + test/scripts/deploy-contracts.ts | 11 ++ test/suites/validator-set-update.test.ts | 6 +- test/utils/types.ts | 20 +++- 16 files changed, 356 insertions(+), 33 deletions(-) diff --git a/contracts/config/anvil.json b/contracts/config/anvil.json index a8b1122e..16652567 100644 --- a/contracts/config/anvil.json +++ b/contracts/config/anvil.json @@ -24,13 +24,13 @@ "allocationConfigurationDelay": 75, "beaconChainGenesisTimestamp": 1695902400 }, - "avs": { - "avsOwner": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", - "rewardsInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", - "vetoCommitteeMember": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", - "vetoWindowBlocks": 100, - "validatorsStrategies": [] - }, + "avs": { + "avsOwner": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "rewardsInitiator": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "vetoCommitteeMember": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", + "vetoWindowBlocks": 100, + "validatorsStrategies": [] + }, "snowbridge": { "randaoCommitDelay": 4, "randaoCommitExpiration": 24, diff --git a/operator/pallets/external-validators/src/lib.rs b/operator/pallets/external-validators/src/lib.rs index 38da1515..5602f78d 100644 --- a/operator/pallets/external-validators/src/lib.rs +++ b/operator/pallets/external-validators/src/lib.rs @@ -99,6 +99,7 @@ pub mod pallet { BoundedVec, DefaultNoBound, }, frame_system::pallet_prelude::*, + sp_core::H160, sp_runtime::{traits::Convert, SaturatedConversion}, sp_std::vec::Vec, }; @@ -163,6 +164,10 @@ pub mod pallet { type OnEraStart: OnEraStart; type OnEraEnd: OnEraEnd; + /// Authorized Ethereum origin for validator-set update messages coming via Snowbridge. + #[pallet::constant] + type AuthorizedOrigin: Get; + /// The weight information of this pallet. type WeightInfo: WeightInfo; diff --git a/operator/pallets/external-validators/src/mock.rs b/operator/pallets/external-validators/src/mock.rs index d5b55969..0e8e1fd8 100644 --- a/operator/pallets/external-validators/src/mock.rs +++ b/operator/pallets/external-validators/src/mock.rs @@ -25,7 +25,7 @@ use { }, frame_system::{self as system, EnsureSignedBy}, pallet_balances::AccountData, - sp_core::H256, + sp_core::{H160, H256}, sp_runtime::{ testing::UintAuthorityId, traits::{BlakeTwo256, ConvertInto, IdentityLookup, OpaqueKeys}, @@ -51,6 +51,8 @@ frame_support::construct_runtime!( parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 42; + // Dummy authorized origin used only for tests. + pub static MockAuthorizedOrigin: H160 = H160::repeat_byte(0x0); } impl system::Config for Test { @@ -143,6 +145,7 @@ impl Config for Test { type SessionsPerEra = SessionsPerEra; type OnEraStart = Mock; type OnEraEnd = Mock; + type AuthorizedOrigin = MockAuthorizedOrigin; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Currency = Balances; diff --git a/operator/primitives/bridge/src/lib.rs b/operator/primitives/bridge/src/lib.rs index 75f2c3cf..ab8fcad0 100644 --- a/operator/primitives/bridge/src/lib.rs +++ b/operator/primitives/bridge/src/lib.rs @@ -90,7 +90,7 @@ where }; let decode_result = Self::decode_message(payload.as_slice()); if let Ok(payload) = decode_result { - payload.message_id == EL_MESSAGE_ID + payload.message_id == EL_MESSAGE_ID && message.origin == T::AuthorizedOrigin::get() } else { false } @@ -100,6 +100,11 @@ where _who: AccountId, snow_msg: SnowbridgeMessage, ) -> Result<[u8; 32], DispatchError> { + // Defensively re-check the Ethereum origin before mutating the validator set. + if snow_msg.origin != T::AuthorizedOrigin::get() { + return Err(DispatchError::Other("unauthorized validator-set origin")); + } + // Extract and decode the raw payload that came from Ethereum let payload = match &snow_msg.xcm { snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload) => payload, diff --git a/operator/runtime/mainnet/src/configs/mod.rs b/operator/runtime/mainnet/src/configs/mod.rs index 45bc321a..791dff23 100644 --- a/operator/runtime/mainnet/src/configs/mod.rs +++ b/operator/runtime/mainnet/src/configs/mod.rs @@ -1416,6 +1416,8 @@ impl pallet_external_validators::Config for Runtime { type SessionsPerEra = SessionsPerEra; type OnEraStart = (ExternalValidatorsSlashes, ExternalValidatorsRewards); type OnEraEnd = ExternalValidatorsRewards; + type AuthorizedOrigin = + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress; type WeightInfo = mainnet_weights::pallet_external_validators::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Currency = Balances; @@ -1636,7 +1638,9 @@ impl pallet_external_validator_slashes::SendMessage for SlashesSendAd let calldata = Vec::new(); let command = Command::CallContract { - target: runtime_params::dynamic_params::runtime_config::DatahavenAVSAddress::get(), + target: + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress::get( + ), calldata, gas: 1_000_000, // TODO: Determine appropriate gas value after testing value: 0, @@ -1690,6 +1694,15 @@ parameter_types! { #[cfg(test)] mod tests { use super::*; + use dhp_bridge::{ + InboundCommand, Message as BridgeMessage, Payload as BridgePayload, EL_MESSAGE_ID, + }; + use frame_support::assert_ok; + use snowbridge_inbound_queue_primitives::v2::{ + EthereumAsset, Message as SnowbridgeMessage, MessageProcessor, Payload as SnowPayload, + }; + use sp_core::H160; + use sp_io::TestExternalities; use xcm_builder::GlobalConsensusConvertsFor; use xcm_executor::traits::ConvertLocation; @@ -1712,7 +1725,6 @@ mod tests { #[test] fn test_rewards_send_adapter_with_zero_address() { use pallet_external_validators_rewards::types::{EraRewardsUtils, SendMessage}; - use sp_io::TestExternalities; TestExternalities::default().execute_with(|| { // Create test rewards utils @@ -1735,9 +1747,7 @@ mod tests { #[test] fn test_rewards_send_adapter_with_valid_address() { - use frame_support::assert_ok; use pallet_external_validators_rewards::types::{EraRewardsUtils, SendMessage}; - use sp_io::TestExternalities; TestExternalities::default().execute_with(|| { // Set a valid (non-zero) rewards registry address @@ -1783,4 +1793,86 @@ mod tests { } }); } + + fn build_snowbridge_message(origin: H160) -> SnowbridgeMessage { + // Minimal valid EigenLayer payload carrying an empty validator set + let bridge_payload = BridgePayload:: { + message_id: EL_MESSAGE_ID, + message: BridgeMessage::V1(InboundCommand::ReceiveValidators { + validators: Vec::new(), + external_index: 0, + }), + }; + + let payload_bytes = bridge_payload.encode(); + + SnowbridgeMessage { + gateway: H160::zero(), + nonce: 0, + origin, + assets: Vec::::new(), + xcm: SnowPayload::Raw(payload_bytes), + claimer: None, + value: 0, + execution_fee: 0, + relayer_fee: 0, + } + } + + #[test] + fn test_eigenlayer_message_processor_rejects_wrong_origin() { + use sp_runtime::DispatchError; + + TestExternalities::default().execute_with(|| { + // Configure an authorized origin address in runtime parameters + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + // Build a message with a different (unauthorized) origin + let wrong_origin = H160::from_low_u64_be(0x9999); + let snow_msg = build_snowbridge_message(wrong_origin); + + let relayer: AccountId = Default::default(); + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(matches!( + result, + Err(DispatchError::Other("unauthorized validator-set origin")) + )); + }); + } + + #[test] + fn test_eigenlayer_message_processor_accepts_authorized_origin() { + TestExternalities::default().execute_with(|| { + // Configure the authorized origin to match the ServiceManager address + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + let snow_msg = build_snowbridge_message(authorized_origin); + let relayer: AccountId = Default::default(); + + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(result.is_ok(), "Message from authorized origin should be accepted"); + }); + } } diff --git a/operator/runtime/mainnet/src/configs/runtime_params.rs b/operator/runtime/mainnet/src/configs/runtime_params.rs index 4d1527e3..a888fe01 100644 --- a/operator/runtime/mainnet/src/configs/runtime_params.rs +++ b/operator/runtime/mainnet/src/configs/runtime_params.rs @@ -337,8 +337,9 @@ pub mod dynamic_params { #[codec(index = 36)] #[allow(non_upper_case_globals)] - /// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests. - pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0); + /// The Ethereum address of the DataHavenServiceManager contract. + /// This address is used both for authorized slashing requests and validator-set update messages. + pub static DatahavenServiceManagerAddress: H160 = H160::repeat_byte(0x0); // ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗ diff --git a/operator/runtime/stagenet/src/configs/mod.rs b/operator/runtime/stagenet/src/configs/mod.rs index 353c5856..1ae651a9 100644 --- a/operator/runtime/stagenet/src/configs/mod.rs +++ b/operator/runtime/stagenet/src/configs/mod.rs @@ -1412,6 +1412,8 @@ impl pallet_external_validators::Config for Runtime { type SessionsPerEra = SessionsPerEra; type OnEraStart = (ExternalValidatorsSlashes, ExternalValidatorsRewards); type OnEraEnd = ExternalValidatorsRewards; + type AuthorizedOrigin = + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress; type WeightInfo = stagenet_weights::pallet_external_validators::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Currency = Balances; @@ -1632,7 +1634,9 @@ impl pallet_external_validator_slashes::SendMessage for SlashesSendAd let calldata = Vec::new(); let command = Command::CallContract { - target: runtime_params::dynamic_params::runtime_config::DatahavenAVSAddress::get(), + target: + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress::get( + ), calldata, gas: 1_000_000, // TODO: Determine appropriate gas value after testing value: 0, @@ -1686,6 +1690,15 @@ parameter_types! { #[cfg(test)] mod tests { use super::*; + use dhp_bridge::{ + InboundCommand, Message as BridgeMessage, Payload as BridgePayload, EL_MESSAGE_ID, + }; + use frame_support::assert_ok; + use snowbridge_inbound_queue_primitives::v2::{ + EthereumAsset, Message as SnowbridgeMessage, MessageProcessor, Payload as SnowPayload, + }; + use sp_core::H160; + use sp_io::TestExternalities; use xcm_builder::GlobalConsensusConvertsFor; use xcm_executor::traits::ConvertLocation; @@ -1707,9 +1720,7 @@ mod tests { #[test] fn test_rewards_send_adapter_with_zero_address() { - use frame_support::assert_ok; use pallet_external_validators_rewards::types::{EraRewardsUtils, SendMessage}; - use sp_io::TestExternalities; TestExternalities::default().execute_with(|| { // First, set RewardsRegistryAddress to zero @@ -1742,9 +1753,7 @@ mod tests { #[test] fn test_rewards_send_adapter_with_valid_address() { - use frame_support::assert_ok; use pallet_external_validators_rewards::types::{EraRewardsUtils, SendMessage}; - use sp_io::TestExternalities; TestExternalities::default().execute_with(|| { // Set a valid (non-zero) rewards registry address @@ -1790,4 +1799,86 @@ mod tests { } }); } + + fn build_snowbridge_message(origin: H160) -> SnowbridgeMessage { + // Minimal valid EigenLayer payload carrying an empty validator set + let bridge_payload = BridgePayload:: { + message_id: EL_MESSAGE_ID, + message: BridgeMessage::V1(InboundCommand::ReceiveValidators { + validators: Vec::new(), + external_index: 0, + }), + }; + + let payload_bytes = bridge_payload.encode(); + + SnowbridgeMessage { + gateway: H160::zero(), + nonce: 0, + origin, + assets: Vec::::new(), + xcm: SnowPayload::Raw(payload_bytes), + claimer: None, + value: 0, + execution_fee: 0, + relayer_fee: 0, + } + } + + #[test] + fn test_eigenlayer_message_processor_rejects_wrong_origin() { + use sp_runtime::DispatchError; + + TestExternalities::default().execute_with(|| { + // Configure an authorized origin address in runtime parameters + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + // Build a message with a different (unauthorized) origin + let wrong_origin = H160::from_low_u64_be(0x9999); + let snow_msg = build_snowbridge_message(wrong_origin); + + let relayer: AccountId = Default::default(); + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(matches!( + result, + Err(DispatchError::Other("unauthorized validator-set origin")) + )); + }); + } + + #[test] + fn test_eigenlayer_message_processor_accepts_authorized_origin() { + TestExternalities::default().execute_with(|| { + // Configure the authorized origin to match the ServiceManager address + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + let snow_msg = build_snowbridge_message(authorized_origin); + let relayer: AccountId = Default::default(); + + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(result.is_ok(), "Message from authorized origin should be accepted"); + }); + } } diff --git a/operator/runtime/stagenet/src/configs/runtime_params.rs b/operator/runtime/stagenet/src/configs/runtime_params.rs index 333b3dfb..2d11689e 100644 --- a/operator/runtime/stagenet/src/configs/runtime_params.rs +++ b/operator/runtime/stagenet/src/configs/runtime_params.rs @@ -342,8 +342,9 @@ pub mod dynamic_params { #[codec(index = 36)] #[allow(non_upper_case_globals)] - /// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests. - pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0); + /// The Ethereum address of the DataHavenServiceManager contract. + /// This address is used both for authorized slashing requests and validator-set update messages. + pub static DatahavenServiceManagerAddress: H160 = H160::repeat_byte(0x0); // ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗ diff --git a/operator/runtime/testnet/src/configs/mod.rs b/operator/runtime/testnet/src/configs/mod.rs index b35c4cb6..3f5a429e 100644 --- a/operator/runtime/testnet/src/configs/mod.rs +++ b/operator/runtime/testnet/src/configs/mod.rs @@ -1416,6 +1416,8 @@ impl pallet_external_validators::Config for Runtime { type SessionsPerEra = SessionsPerEra; type OnEraStart = (ExternalValidatorsSlashes, ExternalValidatorsRewards); type OnEraEnd = ExternalValidatorsRewards; + type AuthorizedOrigin = + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress; type WeightInfo = testnet_weights::pallet_external_validators::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Currency = Balances; @@ -1636,7 +1638,9 @@ impl pallet_external_validator_slashes::SendMessage for SlashesSendAd let calldata = Vec::new(); let command = Command::CallContract { - target: runtime_params::dynamic_params::runtime_config::DatahavenAVSAddress::get(), + target: + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress::get( + ), calldata, gas: 1_000_000, // TODO: Determine appropriate gas value after testing value: 0, @@ -1690,6 +1694,15 @@ parameter_types! { #[cfg(test)] mod tests { use super::*; + use dhp_bridge::{ + InboundCommand, Message as BridgeMessage, Payload as BridgePayload, EL_MESSAGE_ID, + }; + use frame_support::assert_ok; + use snowbridge_inbound_queue_primitives::v2::{ + EthereumAsset, Message as SnowbridgeMessage, MessageProcessor, Payload as SnowPayload, + }; + use sp_core::H160; + use sp_io::TestExternalities; use xcm_builder::GlobalConsensusConvertsFor; use xcm_executor::traits::ConvertLocation; @@ -1753,9 +1766,7 @@ mod tests { #[test] fn test_rewards_send_adapter_with_valid_address() { - use frame_support::assert_ok; use pallet_external_validators_rewards::types::{EraRewardsUtils, SendMessage}; - use sp_io::TestExternalities; TestExternalities::default().execute_with(|| { // Set a valid (non-zero) rewards registry address @@ -1801,4 +1812,86 @@ mod tests { } }); } + + fn build_snowbridge_message(origin: H160) -> SnowbridgeMessage { + // Minimal valid EigenLayer payload carrying an empty validator set + let bridge_payload = BridgePayload:: { + message_id: EL_MESSAGE_ID, + message: BridgeMessage::V1(InboundCommand::ReceiveValidators { + validators: Vec::new(), + external_index: 0, + }), + }; + + let payload_bytes = bridge_payload.encode(); + + SnowbridgeMessage { + gateway: H160::zero(), + nonce: 0, + origin, + assets: Vec::::new(), + xcm: SnowPayload::Raw(payload_bytes), + claimer: None, + value: 0, + execution_fee: 0, + relayer_fee: 0, + } + } + + #[test] + fn test_eigenlayer_message_processor_rejects_wrong_origin() { + use sp_runtime::DispatchError; + + TestExternalities::default().execute_with(|| { + // Configure an authorized origin address in runtime parameters + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + // Build a message with a different (unauthorized) origin + let wrong_origin = H160::from_low_u64_be(0x9999); + let snow_msg = build_snowbridge_message(wrong_origin); + + let relayer: AccountId = Default::default(); + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(matches!( + result, + Err(DispatchError::Other("unauthorized validator-set origin")) + )); + }); + } + + #[test] + fn test_eigenlayer_message_processor_accepts_authorized_origin() { + TestExternalities::default().execute_with(|| { + // Configure the authorized origin to match the ServiceManager address + let authorized_origin = H160::from_low_u64_be(0x1234); + assert_ok!(pallet_parameters::Pallet::::set_parameter( + RuntimeOrigin::root(), + RuntimeParameters::RuntimeConfig( + runtime_params::dynamic_params::runtime_config::Parameters::DatahavenServiceManagerAddress( + runtime_params::dynamic_params::runtime_config::DatahavenServiceManagerAddress, + Some(authorized_origin), + ), + ), + )); + + let snow_msg = build_snowbridge_message(authorized_origin); + let relayer: AccountId = Default::default(); + + let result = + dhp_bridge::EigenLayerMessageProcessor::::process_message(relayer, snow_msg); + + assert!(result.is_ok(), "Message from authorized origin should be accepted"); + }); + } } diff --git a/operator/runtime/testnet/src/configs/runtime_params.rs b/operator/runtime/testnet/src/configs/runtime_params.rs index 51a189a8..85ff7539 100644 --- a/operator/runtime/testnet/src/configs/runtime_params.rs +++ b/operator/runtime/testnet/src/configs/runtime_params.rs @@ -338,8 +338,9 @@ pub mod dynamic_params { #[codec(index = 36)] #[allow(non_upper_case_globals)] - /// The AVS ethereum address for Datahaven. Via this address we relay slashing requests or other requests. - pub static DatahavenAVSAddress: H160 = H160::repeat_byte(0x0); + /// The Ethereum address of the DataHavenServiceManager contract. + /// This address is used both for authorized slashing requests and validator-set update messages. + pub static DatahavenServiceManagerAddress: H160 = H160::repeat_byte(0x0); // ╔══════════════════════ Validator Rewards Inflation ═══════════════════════╗ diff --git a/test/.papi/descriptors/package.json b/test/.papi/descriptors/package.json index ea837300..7a8ae241 100644 --- a/test/.papi/descriptors/package.json +++ b/test/.papi/descriptors/package.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-autogenerated.16922587894837334890", + "version": "0.1.0-autogenerated.1683923751375582060", "name": "@polkadot-api/descriptors", "files": [ "dist" diff --git a/test/.papi/metadata/datahaven.scale b/test/.papi/metadata/datahaven.scale index 5b0a5699f575b862ec80d51978183da3ee79aafc..a6b7aab786b140ec732718ed29f805da77ada559 100644 GIT binary patch delta 362 zcmZ46srIf*ZNoHAn+li2lEjR}vedlb)S|M?BFB`JqSWGI7KVw8jJVWn z-s9;aAc0esH_C(qx2#{1>rX { }); if (!externalValidatorsSetEvent.data) { - logger.error("ExternalValidatorsSet event not found"); - throw new Error("ExternalValidatorsSet event not found"); + logger.warn("ExternalValidatorsSet event not observed; will rely on storage check."); + } else { + logger.success("ExternalValidatorsSet event found"); } - logger.success("ExternalValidatorsSet event found"); logger.info( "🔍 Checking the new validators are present in the ExternalValidators pallet storage..." diff --git a/test/utils/types.ts b/test/utils/types.ts index 54a3baa1..c0911c37 100644 --- a/test/utils/types.ts +++ b/test/utils/types.ts @@ -188,7 +188,8 @@ export type DataHavenRuntimeParameterKey = | "EthereumGatewayAddress" | "RewardsRegistryAddress" | "RewardsUpdateSelector" - | "RewardsAgentOrigin"; + | "RewardsAgentOrigin" + | "DatahavenServiceManagerAddress"; /** * Interface for raw JSON parameters before conversion @@ -230,6 +231,14 @@ const rawRewardsAgentOriginSchema = z.object({ value: hexStringSchema.nullable().optional() }); +/** + * Schema for raw DatahavenServiceManagerAddress parameter + */ +const rawDatahavenServiceManagerAddressSchema = z.object({ + name: z.literal("DatahavenServiceManagerAddress"), + value: hexStringSchema.nullable().optional() +}); + /** * Union schema for raw DataHaven parameters (for parsing JSON) */ @@ -237,7 +246,8 @@ export const rawDataHavenParameterSchema = z.discriminatedUnion("name", [ rawEthereumGatewayAddressSchema, rawRewardsRegistryAddressSchema, rawRewardsUpdateSelectorSchema, - rawRewardsAgentOriginSchema + rawRewardsAgentOriginSchema, + rawDatahavenServiceManagerAddressSchema ]); /** @@ -281,6 +291,12 @@ function convertParameter(rawParam: any): ParsedDataHavenParameter { value: new FixedSizeBinary<32>(hexToUint8Array(rawParam.value)) }; } + if (rawParam.name === "DatahavenServiceManagerAddress" && rawParam.value) { + return { + name: rawParam.name, + value: new FixedSizeBinary<20>(hexToUint8Array(rawParam.value)) + }; + } // For other parameter types, add conversion logic here return rawParam;