mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
feat: 🏗️ run execution relayer (#73)
## This PR includes: - Running the execution relayer on the CLI - Modifying the Payload generation in the `DataHavenServiceManager.sol` - Modified the `EigenLayerMessageProcessor` to work with the ValidatorSet update message, but for this change we are loosing the generic message type (it was the only way to make it work so far). - Adds a `--no-wait` argument to the cli launch and stop commands to bootstrap faster. ### Testing the Snowbridge message encoding / decoding - Added`MessageEncoding.t.sol` is documented and explains how to generate bin data to use in the rust test. - Added a Rust unit test to `EigenLayerMessageProcessor` to compare the message encoding/decoding taking the bytes from a file (previously generated with some mock data). Specifically, we want that:3cbca0db6d/contracts/src/libraries/DataHavenSnowbridgeMessages.sol (L78-L85)Generates the right bytes encoding for0e2c9cd518/operator/primitives/bridge/src/lib.rs (L51)If the test passes, it's very likely that the CLI will also pass, if not, then we might wanna check something else is missing. ### Breaking change ⚠️ For compatibility reasons with Snowbridge contracts (they call specific extrinsics of specific pallets), I had to rename: - `InboundQueueV2` -> `EthereumInboundQueueV2` - `OutboundQueueV2` -> `EthereumOutboundQueueV2` ## For follow up PRs: - Add an automated way of generating the Solidity bytes fo testing, so we don't need to maintain the MessageEncoding.t.sol and generate the binary data manually. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added support for the "execution" relayer type in relay configuration, parsing, and CLI launch utilities. - Introduced a Solidity test contract for encoding and logging validator set messages. - Added a comprehensive "start:all" script to streamline launching and setup processes. - **Enhancements** - Improved message encoding for validator set updates, aligning with new struct field names and message formats. - Updated relay configuration schema and validation to support execution relayers and OFAC settings. - Increased beacon datastore capacity and adjusted relay scheduling parameters in configuration files. - **Refactor** - Renamed runtime type aliases for inbound/outbound queues to more descriptive names across mainnet, stagenet, and testnet. - Centralized and streamlined validator set update logic in CLI utilities. - Centralized message decoding logic and improved visibility of message fields in Rust components. - **Bug Fixes** - Improved error handling and decoding logic for message processing in Rust components. - **Tests** - Added Rust and Solidity tests for message encoding and processing validation. - **Chores** - Updated dependencies and feature flags in Rust project configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com>
This commit is contained in:
parent
9f55e10339
commit
f07afda0b0
20 changed files with 414 additions and 137 deletions
|
|
@ -118,7 +118,7 @@ contract DataHavenServiceManager is ServiceManagerBase, IDataHavenServiceManager
|
|||
newValidatorSet[i] = validatorEthAddressToSolochainAddress[currentValidatorSet[i]];
|
||||
}
|
||||
DataHavenSnowbridgeMessages.NewValidatorSetPayload memory newValidatorSetPayload =
|
||||
DataHavenSnowbridgeMessages.NewValidatorSetPayload({newValidatorSet: newValidatorSet});
|
||||
DataHavenSnowbridgeMessages.NewValidatorSetPayload({validators: newValidatorSet});
|
||||
DataHavenSnowbridgeMessages.NewValidatorSet memory newValidatorSetMessage =
|
||||
DataHavenSnowbridgeMessages.NewValidatorSet({
|
||||
nonce: 0,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,18 @@ pragma solidity ^0.8.27;
|
|||
import {ScaleCodec} from "snowbridge/src/utils/ScaleCodec.sol";
|
||||
|
||||
library DataHavenSnowbridgeMessages {
|
||||
// Message ID. This is not expected to change and comes from the runtime.
|
||||
// See EigenLayerMessageProcessor in primitives/bridge/src/lib.rs.
|
||||
bytes4 constant EL_MESSAGE_ID = 0x70150038;
|
||||
|
||||
enum Message {
|
||||
V0
|
||||
}
|
||||
|
||||
enum OutboundCommandV1 {
|
||||
ReceiveValidators
|
||||
}
|
||||
|
||||
/**
|
||||
* @title New Validator Set Snowbridge Message
|
||||
* @notice A struct representing a new validator set to be sent as a message through Snowbridge.
|
||||
|
|
@ -25,13 +37,12 @@ library DataHavenSnowbridgeMessages {
|
|||
/**
|
||||
* @title New Validator Set Snowbridge Message Payload
|
||||
* @notice A struct representing the payload of a new validator set message.
|
||||
* !IMPORTANT: The fields in this struct are placeholder until we have the actual message format
|
||||
* ! defined in the DataHaven solochain.
|
||||
* This mimics the message format defined in the InboundQueueV2 pallet of the DataHaven
|
||||
* solochain.
|
||||
*/
|
||||
struct NewValidatorSetPayload {
|
||||
/// @notice The new validator set. This should be interpreted as the list of
|
||||
/// validator addresses in the DataHaven network.
|
||||
bytes32[] newValidatorSet;
|
||||
/// @notice The list of validators in the DataHaven network.
|
||||
bytes32[] validators;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,13 +68,23 @@ library DataHavenSnowbridgeMessages {
|
|||
function scaleEncodeNewValidatorSetMessagePayload(
|
||||
NewValidatorSetPayload memory payload
|
||||
) public pure returns (bytes memory) {
|
||||
// Encode all fields into a buffer.
|
||||
bytes memory accum = hex"";
|
||||
for (uint256 i = 0; i < payload.newValidatorSet.length; i++) {
|
||||
accum = bytes.concat(accum, payload.newValidatorSet[i]);
|
||||
uint32 validatorsLen = uint32(payload.validators.length);
|
||||
bytes32[] memory validatorSet = payload.validators;
|
||||
// TODO: This shouldn't be hardcoded, but set to the corresponding epoch of this validator set.
|
||||
uint48 epoch = 0;
|
||||
bytes memory validatorsFlattened;
|
||||
for (uint32 i = 0; i < validatorSet.length; i++) {
|
||||
validatorsFlattened =
|
||||
bytes.concat(validatorsFlattened, abi.encodePacked(validatorSet[i]));
|
||||
}
|
||||
// Encode number of validator addresses, followed by encoded validator addresses.
|
||||
return
|
||||
bytes.concat(ScaleCodec.checkedEncodeCompactU32(payload.newValidatorSet.length), accum);
|
||||
|
||||
return bytes.concat(
|
||||
EL_MESSAGE_ID,
|
||||
bytes1(uint8(Message.V0)),
|
||||
bytes1(uint8(OutboundCommandV1.ReceiveValidators)),
|
||||
ScaleCodec.encodeCompactU32(validatorsLen),
|
||||
validatorsFlattened,
|
||||
ScaleCodec.encodeU64(uint64(epoch))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
contracts/test/MessageEncoding.t.sol
Normal file
44
contracts/test/MessageEncoding.t.sol
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.27;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
import {DataHavenSnowbridgeMessages} from "../src/libraries/DataHavenSnowbridgeMessages.sol";
|
||||
|
||||
// This test is used to encode the receive validators message and log the hex string.
|
||||
// The hex string is then used to generate the .bin file for the Rust test.
|
||||
// To generate the .bin file, run:
|
||||
// forge test --match-test testEncodeReceiveValidatorsMessageAndLog
|
||||
// Then, copy the hex string and paste it into the Rust test file.
|
||||
// Then, run:
|
||||
// cargo test --test decode_receive_validators_message_from_file_correctly
|
||||
// The test should pass.
|
||||
contract MessageEncodingTest is Test {
|
||||
function testEncodeReceiveValidatorsMessageAndLog() public pure {
|
||||
// Mock Data -
|
||||
uint64 mockNonce = 12345;
|
||||
bytes32 mockTopic = 0x123456789012345678901234567890123456789012345678901234567890abcd;
|
||||
|
||||
bytes32[] memory mockValidators = new bytes32[](2);
|
||||
mockValidators[0] = 0x0000000000000000000000000000000000000000000000000000000000000001;
|
||||
mockValidators[1] = 0x0000000000000000000000000000000000000000000000000000000000000002;
|
||||
// uint64 mockEpoch = 0; // This is hardcoded to 0 in the Solidity function's payload part
|
||||
|
||||
DataHavenSnowbridgeMessages.NewValidatorSetPayload memory newValidatorSetPayload =
|
||||
DataHavenSnowbridgeMessages.NewValidatorSetPayload({validators: mockValidators});
|
||||
// epoch is implicitly 0 in scaleEncodeNewValidatorSetMessagePayload
|
||||
|
||||
DataHavenSnowbridgeMessages.NewValidatorSet memory newValidatorSetMessage =
|
||||
DataHavenSnowbridgeMessages.NewValidatorSet({
|
||||
nonce: mockNonce,
|
||||
topic: mockTopic,
|
||||
payload: newValidatorSetPayload
|
||||
});
|
||||
|
||||
bytes memory encodedMessage =
|
||||
DataHavenSnowbridgeMessages.scaleEncodeNewValidatorSetMessage(newValidatorSetMessage);
|
||||
|
||||
console.log("Encoded NewValidatorSet message (hex):");
|
||||
console.logBytes(encodedMessage); // This will print the hex string
|
||||
}
|
||||
}
|
||||
1
operator/Cargo.lock
generated
1
operator/Cargo.lock
generated
|
|
@ -2821,6 +2821,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"hex",
|
||||
"pallet-external-validators",
|
||||
"parity-scale-codec",
|
||||
"snowbridge-core 0.2.0",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ parity-scale-codec = { workspace = true }
|
|||
snowbridge-core = { workspace = true }
|
||||
snowbridge-inbound-queue-primitives = { workspace = true }
|
||||
sp-std = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
|
@ -25,4 +26,5 @@ std = [
|
|||
"parity-scale-codec/std",
|
||||
"pallet-external-validators/std",
|
||||
"sp-std/std",
|
||||
"snowbridge-inbound-queue-primitives/std",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@ use parity_scale_codec::DecodeAll;
|
|||
use snowbridge_inbound_queue_primitives::v2::{Message as SnowbridgeMessage, MessageProcessor};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub const EL_MESSAGE_ID: [u8; 4] = [112, 21, 0, 56];
|
||||
// Message ID. This is not expected to change and its arbitrary bytes defined here.
|
||||
// It should match the EL_MESSAGE_ID in DataHavenSnowbridgeMessages.sol
|
||||
pub const EL_MESSAGE_ID: [u8; 4] = [112, 21, 0, 56]; // 0x70150038
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct Payload<T>
|
||||
where
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
message: Message<T>,
|
||||
message_id: [u8; 4],
|
||||
pub message: Message<T>,
|
||||
pub message_id: [u8; 4],
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
|
|
@ -38,6 +40,20 @@ where
|
|||
/// EigenLayer Message Processor
|
||||
pub struct EigenLayerMessageProcessor<T>(PhantomData<T>);
|
||||
|
||||
impl<T> EigenLayerMessageProcessor<T>
|
||||
where
|
||||
T: pallet_external_validators::Config,
|
||||
{
|
||||
pub fn decode_message(mut payload: &[u8]) -> Result<Payload<T>, DispatchError> {
|
||||
let decode_result = Payload::<T>::decode_all(&mut payload);
|
||||
if let Ok(payload) = decode_result {
|
||||
Ok(payload)
|
||||
} else {
|
||||
Err(DispatchError::Other("unable to parse the message payload"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, AccountId> MessageProcessor<AccountId> for EigenLayerMessageProcessor<T>
|
||||
where
|
||||
T: pallet_external_validators::Config,
|
||||
|
|
@ -50,7 +66,7 @@ where
|
|||
network: _,
|
||||
} => return false,
|
||||
};
|
||||
let decode_result = Payload::<T>::decode_all(&mut payload.as_slice());
|
||||
let decode_result = Self::decode_message(payload.as_slice());
|
||||
if let Ok(payload) = decode_result {
|
||||
payload.message_id == EL_MESSAGE_ID
|
||||
} else {
|
||||
|
|
@ -69,7 +85,7 @@ where
|
|||
network: _,
|
||||
} => return Err(DispatchError::Other("Invalid Message")),
|
||||
};
|
||||
let decode_result = Payload::<T>::decode_all(&mut payload.as_slice());
|
||||
let decode_result = Self::decode_message(payload.as_slice());
|
||||
let message = if let Ok(payload) = decode_result {
|
||||
payload.message
|
||||
} else {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -27,11 +27,12 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, ExternalValidatorsRewards, Hash,
|
||||
Historical, ImOnline, MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore,
|
||||
OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason,
|
||||
RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys, Signature, System,
|
||||
Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExternalValidators,
|
||||
ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Runtime, RuntimeCall,
|
||||
RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session,
|
||||
SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
|
||||
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
|
|
@ -532,7 +533,7 @@ parameter_types! {
|
|||
impl pallet_message_queue::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
type MessageProcessor = OutboundQueueV2;
|
||||
type MessageProcessor = EthereumOutboundQueueV2;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type MessageProcessor =
|
||||
pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>;
|
||||
|
|
@ -698,7 +699,7 @@ impl snowbridge_pallet_system::Config for Runtime {
|
|||
// Implement the Snowbridge System v2 config trait
|
||||
impl snowbridge_pallet_system_v2::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = OutboundQueueV2;
|
||||
type OutboundQueue = EthereumOutboundQueueV2;
|
||||
type FrontendOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type GovernanceOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type WeightInfo = ();
|
||||
|
|
@ -956,10 +957,10 @@ impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapt
|
|||
}
|
||||
|
||||
fn validate(message: Self::Message) -> Result<Self::Ticket, SendError> {
|
||||
OutboundQueueV2::validate(&message)
|
||||
EthereumOutboundQueueV2::validate(&message)
|
||||
}
|
||||
fn deliver(message: Self::Ticket) -> Result<H256, SendError> {
|
||||
OutboundQueueV2::deliver(message)
|
||||
EthereumOutboundQueueV2::deliver(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -322,10 +322,10 @@ mod runtime {
|
|||
pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client;
|
||||
|
||||
#[runtime::pallet_index(61)]
|
||||
pub type InboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
pub type EthereumInboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(62)]
|
||||
pub type OutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
pub type EthereumOutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(63)]
|
||||
pub type SnowbridgeSystem = snowbridge_pallet_system;
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, ExternalValidatorsRewards, Hash,
|
||||
Historical, ImOnline, MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore,
|
||||
OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason,
|
||||
RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys, Signature, System,
|
||||
Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExternalValidators,
|
||||
ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Runtime, RuntimeCall,
|
||||
RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session,
|
||||
SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
|
||||
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
|
|
@ -531,7 +532,7 @@ parameter_types! {
|
|||
impl pallet_message_queue::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
type MessageProcessor = OutboundQueueV2;
|
||||
type MessageProcessor = EthereumOutboundQueueV2;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type MessageProcessor =
|
||||
pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>;
|
||||
|
|
@ -699,7 +700,7 @@ impl snowbridge_pallet_system::Config for Runtime {
|
|||
// Implement the Snowbridge System v2 config trait
|
||||
impl snowbridge_pallet_system_v2::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = OutboundQueueV2;
|
||||
type OutboundQueue = EthereumOutboundQueueV2;
|
||||
type FrontendOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type GovernanceOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type WeightInfo = ();
|
||||
|
|
@ -917,10 +918,10 @@ impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapt
|
|||
}
|
||||
|
||||
fn validate(message: Self::Message) -> Result<Self::Ticket, SendError> {
|
||||
OutboundQueueV2::validate(&message)
|
||||
EthereumOutboundQueueV2::validate(&message)
|
||||
}
|
||||
fn deliver(message: Self::Ticket) -> Result<H256, SendError> {
|
||||
OutboundQueueV2::deliver(message)
|
||||
EthereumOutboundQueueV2::deliver(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -322,10 +322,10 @@ mod runtime {
|
|||
pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client;
|
||||
|
||||
#[runtime::pallet_index(61)]
|
||||
pub type InboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
pub type EthereumInboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(62)]
|
||||
pub type OutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
pub type EthereumOutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(63)]
|
||||
pub type SnowbridgeSystem = snowbridge_pallet_system;
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ mod runtime_params;
|
|||
|
||||
use super::{
|
||||
deposit, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber,
|
||||
EthereumBeaconClient, EvmChainId, ExternalValidators, ExternalValidatorsRewards, Hash,
|
||||
Historical, ImOnline, MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore,
|
||||
OutboundQueueV2, PalletInfo, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason,
|
||||
RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys, Signature, System,
|
||||
Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION, STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExternalValidators,
|
||||
ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, Offences,
|
||||
OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Runtime, RuntimeCall,
|
||||
RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session,
|
||||
SessionKeys, Signature, System, Timestamp, EXISTENTIAL_DEPOSIT, SLOT_DURATION,
|
||||
STORAGE_BYTE_FEE, SUPPLY_FACTOR, UNIT, VERSION,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use datahaven_runtime_common::{
|
||||
|
|
@ -531,7 +532,7 @@ parameter_types! {
|
|||
impl pallet_message_queue::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
type MessageProcessor = OutboundQueueV2;
|
||||
type MessageProcessor = EthereumOutboundQueueV2;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type MessageProcessor =
|
||||
pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>;
|
||||
|
|
@ -697,7 +698,7 @@ impl snowbridge_pallet_system::Config for Runtime {
|
|||
// Implement the Snowbridge System v2 config trait
|
||||
impl snowbridge_pallet_system_v2::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OutboundQueue = OutboundQueueV2;
|
||||
type OutboundQueue = EthereumOutboundQueueV2;
|
||||
type FrontendOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type GovernanceOrigin = EnsureRootWithSuccess<AccountId, RootLocation>;
|
||||
type WeightInfo = ();
|
||||
|
|
@ -955,10 +956,10 @@ impl pallet_external_validators_rewards::types::SendMessage for RewardsSendAdapt
|
|||
}
|
||||
|
||||
fn validate(message: Self::Message) -> Result<Self::Ticket, SendError> {
|
||||
OutboundQueueV2::validate(&message)
|
||||
EthereumOutboundQueueV2::validate(&message)
|
||||
}
|
||||
fn deliver(message: Self::Ticket) -> Result<H256, SendError> {
|
||||
OutboundQueueV2::deliver(message)
|
||||
EthereumOutboundQueueV2::deliver(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -322,10 +322,10 @@ mod runtime {
|
|||
pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client;
|
||||
|
||||
#[runtime::pallet_index(61)]
|
||||
pub type InboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
pub type EthereumInboundQueueV2 = snowbridge_pallet_inbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(62)]
|
||||
pub type OutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
pub type EthereumOutboundQueueV2 = snowbridge_pallet_outbound_queue_v2;
|
||||
|
||||
#[runtime::pallet_index(63)]
|
||||
pub type SnowbridgeSystem = snowbridge_pallet_system;
|
||||
|
|
@ -1091,3 +1091,93 @@ impl_runtime_apis! {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codec::Encode;
|
||||
use dhp_bridge::InboundCommand;
|
||||
use dhp_bridge::{Message, Payload, EL_MESSAGE_ID};
|
||||
use snowbridge_inbound_queue_primitives::v2::{Message as SnowbridgeMessage, MessageProcessor};
|
||||
use sp_core::H256;
|
||||
|
||||
const MOCK_NONCE: u64 = 12345u64;
|
||||
const MOCK_VALIDATORS_HEX: [&str; 2] = [
|
||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||
];
|
||||
const MOCK_EXTERNAL_INDEX: u64 = 0u64;
|
||||
|
||||
fn hex_to_bytes32(hex_str: &str) -> [u8; 32] {
|
||||
let mut arr = [0u8; 32];
|
||||
hex::decode_to_slice(hex_str, &mut arr).expect("Failed to decode hex string to bytes32");
|
||||
arr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eigenlayer_message_processor() {
|
||||
// Create mock validators
|
||||
let validators: Vec<AccountId> = MOCK_VALIDATORS_HEX
|
||||
.iter()
|
||||
.map(|s| hex_to_bytes32(s).into())
|
||||
.collect();
|
||||
|
||||
// Create a mock message payload
|
||||
let message = Message::V1(dhp_bridge::InboundCommand::ReceiveValidators {
|
||||
validators: validators.clone(),
|
||||
external_index: MOCK_EXTERNAL_INDEX,
|
||||
});
|
||||
|
||||
let payload = Payload::<Runtime> {
|
||||
message,
|
||||
message_id: EL_MESSAGE_ID,
|
||||
};
|
||||
|
||||
// Create a mock Snowbridge message
|
||||
let snowbridge_message = SnowbridgeMessage {
|
||||
xcm: snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload.encode()),
|
||||
gateway: H160::default(),
|
||||
nonce: MOCK_NONCE,
|
||||
origin: H160::default(),
|
||||
assets: vec![],
|
||||
claimer: None,
|
||||
value: 0u128,
|
||||
execution_fee: 0u128,
|
||||
relayer_fee: 0u128,
|
||||
};
|
||||
|
||||
// Test can_process_message
|
||||
let mock_account = H256::from_slice(&[1u8; 32]);
|
||||
assert!(
|
||||
dhp_bridge::EigenLayerMessageProcessor::<Runtime>::can_process_message(
|
||||
&mock_account,
|
||||
&snowbridge_message
|
||||
),
|
||||
"Message should be processable"
|
||||
);
|
||||
|
||||
let payload = match &snowbridge_message.xcm {
|
||||
snowbridge_inbound_queue_primitives::v2::Payload::Raw(payload) => payload,
|
||||
_ => panic!("Invalid Message"),
|
||||
};
|
||||
|
||||
let decoded_result =
|
||||
dhp_bridge::EigenLayerMessageProcessor::<Runtime>::decode_message(payload.as_slice());
|
||||
|
||||
let message = if let Ok(payload) = decoded_result {
|
||||
payload.message
|
||||
} else {
|
||||
panic!("unable to parse the message payload");
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::V1(InboundCommand::ReceiveValidators {
|
||||
validators,
|
||||
external_index,
|
||||
}) => {
|
||||
assert_eq!(validators, validators);
|
||||
assert_eq!(external_index, MOCK_EXTERNAL_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
test/bun.lockb
Executable file
BIN
test/bun.lockb
Executable file
Binary file not shown.
|
|
@ -8,7 +8,7 @@ import { launchKurtosis } from "./kurtosis";
|
|||
import { LaunchedNetwork } from "./launchedNetwork";
|
||||
import { launchRelayers } from "./relayer";
|
||||
import { performSummaryOperations } from "./summary";
|
||||
import { performValidatorOperations } from "./validator";
|
||||
import { performValidatorOperations, performValidatorSetUpdate } from "./validator";
|
||||
|
||||
// Non-optional properties determined by having default values
|
||||
export interface LaunchOptions {
|
||||
|
|
@ -93,6 +93,8 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN
|
|||
setParameters: options.setParameters
|
||||
});
|
||||
|
||||
await performValidatorSetUpdate(options, launchedNetwork.elRpcUrl, contractsDeployed);
|
||||
|
||||
await performSummaryOperations(options, launchedNetwork);
|
||||
const fullEnd = performance.now();
|
||||
const fullMinutes = ((fullEnd - timeStart) / (1000 * 60)).toFixed(1);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const RELAYER_CONFIG_DIR = "tmp/configs";
|
|||
const RELAYER_CONFIG_PATHS = {
|
||||
BEACON: path.join(RELAYER_CONFIG_DIR, "beacon-relay.json"),
|
||||
BEEFY: path.join(RELAYER_CONFIG_DIR, "beefy-relay.json"),
|
||||
EXECUTION: path.join(RELAYER_CONFIG_DIR, "execution-relay.json"),
|
||||
SOLOCHAIN: path.join(RELAYER_CONFIG_DIR, "solochain-relay.json")
|
||||
};
|
||||
const INITIAL_CHECKPOINT_FILE = "dump-initial-checkpoint.json";
|
||||
|
|
@ -146,6 +147,15 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La
|
|||
type: "substrate",
|
||||
value: SUBSTRATE_FUNDED_ACCOUNTS.CHARLETH.privateKey
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "relayer-⚙️",
|
||||
type: "execution",
|
||||
config: RELAYER_CONFIG_PATHS.EXECUTION,
|
||||
pk: {
|
||||
type: "substrate",
|
||||
value: SUBSTRATE_FUNDED_ACCOUNTS.BALTATHAR.privateKey
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -179,51 +189,60 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La
|
|||
);
|
||||
|
||||
switch (type) {
|
||||
case "beacon":
|
||||
{
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.datastore.location = "/data";
|
||||
cfg.sink.parachain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
case "beacon": {
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.datastore.location = "/data";
|
||||
cfg.sink.parachain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated beacon config written to ${outputFilePath}`);
|
||||
}
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated beacon config written to ${outputFilePath}`);
|
||||
break;
|
||||
case "beefy":
|
||||
{
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.polkadot.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.sink.contracts.BeefyClient = beefyClientAddress;
|
||||
cfg.sink.contracts.Gateway = gatewayAddress;
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated beefy config written to ${outputFilePath}`);
|
||||
}
|
||||
}
|
||||
case "beefy": {
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.polkadot.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.sink.contracts.BeefyClient = beefyClientAddress;
|
||||
cfg.sink.contracts.Gateway = gatewayAddress;
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated beefy config written to ${outputFilePath}`);
|
||||
break;
|
||||
case "solochain":
|
||||
{
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.source.solochain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
cfg.source.contracts.BeefyClient = beefyClientAddress;
|
||||
cfg.source.contracts.Gateway = gatewayAddress;
|
||||
cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.datastore.location = datastorePath;
|
||||
cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.sink.contracts.Gateway = gatewayAddress;
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated solochain config written to ${outputFilePath}`);
|
||||
}
|
||||
}
|
||||
case "execution": {
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.datastore.location = "/data";
|
||||
cfg.sink.parachain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
cfg.source.contracts.Gateway = gatewayAddress;
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated execution config written to ${outputFilePath}`);
|
||||
break;
|
||||
}
|
||||
case "solochain": {
|
||||
const cfg = parseRelayConfig(json, type);
|
||||
cfg.source.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.source.solochain.endpoint = `ws://${substrateNodeId}:${substrateWsPort}`;
|
||||
cfg.source.contracts.BeefyClient = beefyClientAddress;
|
||||
cfg.source.contracts.Gateway = gatewayAddress;
|
||||
cfg.source.beacon.endpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.stateEndpoint = `http://host.docker.internal:${ethHttpPort}`;
|
||||
cfg.source.beacon.datastore.location = datastorePath;
|
||||
cfg.sink.ethereum.endpoint = `ws://host.docker.internal:${ethWsPort}`;
|
||||
cfg.sink.contracts.Gateway = gatewayAddress;
|
||||
await Bun.write(outputFilePath, JSON.stringify(cfg, null, 4));
|
||||
logger.success(`Updated solochain config written to ${outputFilePath}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invariant(options.relayerImageTag, "❌ Relayer image tag not defined");
|
||||
|
||||
await initEthClientPallet(options, launchedNetwork);
|
||||
await initEthClientPallet(options, launchedNetwork, datastorePath);
|
||||
|
||||
for (const { config, name, type, pk, secondaryPk } of relayersToStart) {
|
||||
try {
|
||||
|
|
@ -250,10 +269,10 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La
|
|||
];
|
||||
|
||||
const volumeMounts: string[] = ["-v", `${hostConfigFilePath}:${containerConfigFilePath}`];
|
||||
const hostDatastorePath = path.resolve(datastorePath);
|
||||
const containerDatastorePath = "/data";
|
||||
|
||||
if (type === "beacon") {
|
||||
const hostDatastorePath = path.resolve(datastorePath);
|
||||
const containerDatastorePath = "/data";
|
||||
if (type === "beacon" || type === "execution") {
|
||||
volumeMounts.push("-v", `${hostDatastorePath}:${containerDatastorePath}`);
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +281,7 @@ export const launchRelayers = async (options: LaunchOptions, launchedNetwork: La
|
|||
type,
|
||||
"--config",
|
||||
config,
|
||||
type === "beacon" ? "--substrate.private-key" : "--ethereum.private-key",
|
||||
`--${pk.type}.private-key`,
|
||||
pk.value
|
||||
];
|
||||
|
||||
|
|
@ -366,11 +385,13 @@ const waitBeefyReady = async (
|
|||
*
|
||||
* @param options - Launch options containing the relayer binary path.
|
||||
* @param launchedNetwork - An instance of LaunchedNetwork to interact with the running network.
|
||||
* @param datastorePath - The path to the datastore directory.
|
||||
* @throws If there's an error generating the beacon checkpoint or submitting it to Substrate.
|
||||
*/
|
||||
export const initEthClientPallet = async (
|
||||
options: LaunchOptions,
|
||||
launchedNetwork: LaunchedNetwork
|
||||
launchedNetwork: LaunchedNetwork,
|
||||
datastorePath: string
|
||||
) => {
|
||||
logger.debug("Initialising eth client pallet");
|
||||
// Poll the beacon chain until it's ready every 10 seconds for 5 minutes
|
||||
|
|
@ -393,9 +414,11 @@ export const initEthClientPallet = async (
|
|||
launchedNetwork.networkName,
|
||||
"❌ Docker network name not found in LaunchedNetwork instance"
|
||||
);
|
||||
const datastoreHostPath = path.resolve(datastorePath);
|
||||
const command = `docker run \
|
||||
-v ${beaconConfigHostPath}:${beaconConfigContainerPath}:ro \
|
||||
-v ${checkpointHostPath}:${checkpointContainerPath} \
|
||||
-v ${datastoreHostPath}:/data \
|
||||
--name generate-beacon-checkpoint \
|
||||
--workdir /app \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
|
|
@ -409,7 +432,9 @@ export const initEthClientPallet = async (
|
|||
const initialCheckpointFile = Bun.file(INITIAL_CHECKPOINT_PATH);
|
||||
const initialCheckpointRaw = await initialCheckpointFile.text();
|
||||
const initialCheckpoint = parseJsonToBeaconCheckpoint(JSON.parse(initialCheckpointRaw));
|
||||
await initialCheckpointFile.delete();
|
||||
if (initialCheckpointFile.delete) {
|
||||
await initialCheckpointFile.delete();
|
||||
}
|
||||
|
||||
logger.trace("Initial checkpoint:");
|
||||
logger.trace(initialCheckpoint.toJSON());
|
||||
|
|
|
|||
|
|
@ -62,37 +62,49 @@ export const performValidatorOperations = async (
|
|||
await setupValidators({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// If not specified, prompt for update
|
||||
let shouldUpdateValidatorSet = options.updateValidatorSet;
|
||||
if (shouldUpdateValidatorSet === undefined) {
|
||||
shouldUpdateValidatorSet = await confirmWithTimeout(
|
||||
"Do you want to update the validator set on the substrate chain?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`🏳️ Using flag option: ${shouldUpdateValidatorSet ? "will update" : "will not update"} validator set`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldUpdateValidatorSet) {
|
||||
if (!contractsDeployed) {
|
||||
logger.warn(
|
||||
"⚠️ Updating validator set but contracts were not deployed in this CLI run. Could have unexpected results."
|
||||
);
|
||||
}
|
||||
|
||||
await updateValidatorSet({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
} else {
|
||||
logger.info("👍 Skipping validator set update");
|
||||
printDivider();
|
||||
}
|
||||
/**
|
||||
* Performs the validator set update operation based on user options
|
||||
* This function is now separate so it can be called after relayers are set up
|
||||
*
|
||||
* @param options - CLI options for the validator set update
|
||||
* @param networkRpcUrl - RPC URL for the Ethereum network
|
||||
* @param contractsDeployed - Flag indicating if contracts were deployed in this CLI run
|
||||
* @returns Promise resolving when the operation is complete
|
||||
*/
|
||||
export const performValidatorSetUpdate = async (
|
||||
options: LaunchOptions,
|
||||
networkRpcUrl: string,
|
||||
contractsDeployed: boolean
|
||||
) => {
|
||||
// If not specified, prompt for update
|
||||
let shouldUpdateValidatorSet = options.updateValidatorSet;
|
||||
if (shouldUpdateValidatorSet === undefined) {
|
||||
shouldUpdateValidatorSet = await confirmWithTimeout(
|
||||
"Do you want to update the validator set on the substrate chain?",
|
||||
true,
|
||||
10
|
||||
);
|
||||
} else {
|
||||
logger.info("👍 Skipping validator setup");
|
||||
logger.info(
|
||||
`🏳️ Using flag option: ${shouldUpdateValidatorSet ? "will update" : "will not update"} validator set`
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldUpdateValidatorSet) {
|
||||
if (!contractsDeployed) {
|
||||
logger.warn(
|
||||
"⚠️ Updating validator set but contracts were not deployed in this CLI run. Could have unexpected results."
|
||||
);
|
||||
}
|
||||
|
||||
await updateValidatorSet({
|
||||
rpcUrl: networkRpcUrl
|
||||
});
|
||||
} else {
|
||||
logger.info("👍 Skipping validator set update");
|
||||
printDivider();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
"contracts": {
|
||||
"Gateway": ""
|
||||
},
|
||||
"channel-id": "",
|
||||
"beacon": {
|
||||
"endpoint": "http://127.0.0.1:9596",
|
||||
"stateEndpoint": "http://127.0.0.1:9596",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"start:e2e:local": "LOG_LEVEL=debug bun start:e2e:ci --bd",
|
||||
"start:e2e:ci": "bun cli launch --datahaven --no-build-datahaven --launch-kurtosis --deploy-contracts --fund-validators --setup-validators --update-validator-set --relayer --clean-network",
|
||||
"start:e2e:minrelayer": "bun cli launch --relayer --deploy-contracts --no-setup-validators --no-update-validator-set --no-fund-validators --datahaven",
|
||||
"start:all": "bun cli launch --datahaven --build-datahaven --launch-kurtosis --deploy-contracts --fund-validators --setup-validators --update-validator-set --relayer --blockscout --verified --clean-network --set-parameters",
|
||||
"stop:docker:datahaven": "docker rm -f $(docker ps -aq --filter name='^datahaven-') 2>/dev/null || true; docker network rm datahaven-net || true",
|
||||
"stop:docker:relayer": "docker rm -f $(docker ps -aq --filter name='^snowbridge-relayer-') 2>/dev/null || true",
|
||||
"stop:e2e": "bun cli stop --all",
|
||||
|
|
|
|||
|
|
@ -105,9 +105,59 @@ export const SolochainRelayConfigSchema = z.object({
|
|||
apiKey: z.string()
|
||||
})
|
||||
});
|
||||
|
||||
export type SolochainRelayConfig = z.infer<typeof SolochainRelayConfigSchema>;
|
||||
|
||||
export type RelayerType = "beefy" | "beacon" | "solochain";
|
||||
export const ExecutionRelayConfigSchema = z.object({
|
||||
source: z.object({
|
||||
ethereum: z.object({
|
||||
endpoint: z.string()
|
||||
}),
|
||||
contracts: z.object({
|
||||
Gateway: z.string()
|
||||
}),
|
||||
beacon: z.object({
|
||||
endpoint: z.string(),
|
||||
stateEndpoint: z.string(),
|
||||
spec: z.object({
|
||||
syncCommitteeSize: z.number(),
|
||||
slotsInEpoch: z.number(),
|
||||
epochsPerSyncCommitteePeriod: z.number(),
|
||||
forkVersions: z.object({
|
||||
deneb: z.number(),
|
||||
electra: z.number()
|
||||
})
|
||||
}),
|
||||
datastore: z.object({
|
||||
location: z.string(),
|
||||
maxEntries: z.number()
|
||||
})
|
||||
})
|
||||
}),
|
||||
sink: z.object({
|
||||
parachain: z.object({
|
||||
endpoint: z.string(),
|
||||
maxWatchedExtrinsics: z.number(),
|
||||
headerRedundancy: z.number()
|
||||
})
|
||||
}),
|
||||
instantVerification: z.boolean(),
|
||||
schedule: z.object({
|
||||
id: z.number().nullable(),
|
||||
totalRelayerCount: z.number(),
|
||||
sleepInterval: z.number()
|
||||
}),
|
||||
ofac: z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
apiKey: z.string()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
export type ExecutionRelayConfig = z.infer<typeof ExecutionRelayConfigSchema>;
|
||||
|
||||
export type RelayerType = "beefy" | "beacon" | "solochain" | "execution";
|
||||
|
||||
/**
|
||||
* Parse beacon relay configuration
|
||||
|
|
@ -143,28 +193,39 @@ function parseSolochainConfig(config: unknown): SolochainRelayConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Type Guard to check if a config object is a BeaconRelayConfig
|
||||
* Parse execution relay configuration
|
||||
*/
|
||||
export function isBeaconConfig(
|
||||
config: BeaconRelayConfig | BeefyRelayConfig
|
||||
): config is BeaconRelayConfig {
|
||||
return "beacon" in config.source;
|
||||
function parseExecutionConfig(config: unknown): ExecutionRelayConfig {
|
||||
const result = ExecutionRelayConfigSchema.safeParse(config);
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
}
|
||||
throw new Error(`Failed to parse config as ExecutionRelayConfig: ${result.error.message}`);
|
||||
}
|
||||
|
||||
export function parseRelayConfig(config: unknown, type: "beacon"): BeaconRelayConfig;
|
||||
export function parseRelayConfig(config: unknown, type: "beefy"): BeefyRelayConfig;
|
||||
export function parseRelayConfig(config: unknown, type: "execution"): ExecutionRelayConfig;
|
||||
export function parseRelayConfig(config: unknown, type: "solochain"): SolochainRelayConfig;
|
||||
export function parseRelayConfig(
|
||||
config: unknown,
|
||||
type: RelayerType
|
||||
): BeaconRelayConfig | BeefyRelayConfig | SolochainRelayConfig;
|
||||
): BeaconRelayConfig | BeefyRelayConfig | ExecutionRelayConfig | SolochainRelayConfig;
|
||||
|
||||
export function parseRelayConfig(
|
||||
config: unknown,
|
||||
type: RelayerType
|
||||
): BeaconRelayConfig | BeefyRelayConfig | SolochainRelayConfig {
|
||||
return type === "beacon"
|
||||
? parseBeaconConfig(config)
|
||||
: type === "beefy"
|
||||
? parseBeefyConfig(config)
|
||||
: parseSolochainConfig(config);
|
||||
): BeaconRelayConfig | BeefyRelayConfig | ExecutionRelayConfig | SolochainRelayConfig {
|
||||
switch (type) {
|
||||
case "beacon":
|
||||
return parseBeaconConfig(config);
|
||||
case "beefy":
|
||||
return parseBeefyConfig(config);
|
||||
case "execution":
|
||||
return parseExecutionConfig(config);
|
||||
case "solochain":
|
||||
return parseSolochainConfig(config);
|
||||
default:
|
||||
throw new Error(`Unknown relayer type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue