diff --git a/crates/primitives/src/abi.rs b/crates/primitives/src/abi.rs index 78877b802..013ed4967 100644 --- a/crates/primitives/src/abi.rs +++ b/crates/primitives/src/abi.rs @@ -189,7 +189,7 @@ macro_rules! define_abi { function zoneId() external view returns (uint32); function sequencer() external view returns (address); - function verifier() external view returns (address); + function zoneFactory() external view returns (address); function sequencerPubkey() external view returns (bytes32); function withdrawalBatchIndex() external view returns (uint64); function blockHash() external view returns (bytes32); @@ -211,6 +211,7 @@ macro_rules! define_abi { function processWithdrawal(Withdrawal calldata withdrawal, bytes32 remainingQueue) external; function submitBatch( + address targetVerifier, uint64 tempoBlockNumber, uint64 recentTempoBlockNumber, BlockTransition calldata blockTransition, @@ -398,7 +399,6 @@ macro_rules! define_abi { address messenger; address initialToken; address sequencer; - address verifier; bytes32 genesisBlockHash; bytes32 genesisTempoBlockHash; uint64 genesisTempoBlockNumber; @@ -414,7 +414,6 @@ macro_rules! define_abi { struct CreateZoneParams { address token; address sequencer; - address verifier; ZoneParams zoneParams; } #[derive(Debug)] @@ -424,13 +423,17 @@ macro_rules! define_abi { address indexed messenger, address token, address sequencer, - address verifier, bytes32 genesisBlockHash, bytes32 genesisTempoBlockHash, uint64 genesisTempoBlockNumber ); function createZone(CreateZoneParams calldata params) external returns (uint32 zoneId, address portal); function verifier() external view returns (address); + function forkVerifier() external view returns (address); + function forkActivationBlock() external view returns (uint64); + function protocolVersion() external view returns (uint64); + function setForkVerifier(address newForkVerifier) external; + function validateVerifier(address targetVerifier, uint64 tempoBlockNumber) external view; function zones(uint32 zoneId) external view returns (ZoneInfo memory); function zoneCount() external view returns (uint32); function isZonePortal(address portal) external view returns (bool); diff --git a/crates/tempo-zone/src/batch.rs b/crates/tempo-zone/src/batch.rs index 8c3c1c676..c6d5acd67 100644 --- a/crates/tempo-zone/src/batch.rs +++ b/crates/tempo-zone/src/batch.rs @@ -101,6 +101,9 @@ pub struct BatchSubmitter { genesis_tempo_block_number: u64, /// Concurrency for pipelined L1 header fetching in ancestry mode. l1_fetch_concurrency: usize, + /// Address of the verifier to pass as `targetVerifier` in `submitBatch`. + /// Read from `ZoneFactory.verifier()` at startup. + target_verifier: Address, } impl BatchSubmitter { @@ -111,6 +114,7 @@ impl BatchSubmitter { portal_address: Address, l1_provider: DynProvider, genesis_tempo_block_number: u64, + target_verifier: Address, ) -> Self { let portal = ZonePortal::new(portal_address, l1_provider.clone()); Self { @@ -119,6 +123,7 @@ impl BatchSubmitter { portal, genesis_tempo_block_number, l1_fetch_concurrency: 16, + target_verifier, } } @@ -201,6 +206,7 @@ impl BatchSubmitter { let pending = self .portal .submitBatch( + self.target_verifier, batch.tempo_block_number, recent_tempo_block_number, block_transition, diff --git a/crates/tempo-zone/src/zonemonitor.rs b/crates/tempo-zone/src/zonemonitor.rs index 4e9a3c0e0..99a70452c 100644 --- a/crates/tempo-zone/src/zonemonitor.rs +++ b/crates/tempo-zone/src/zonemonitor.rs @@ -38,7 +38,7 @@ use tracing::{error, info, instrument, warn}; use alloy_sol_types::{ContractError, SolInterface as _}; use crate::{ - abi::{self, TempoState, ZoneInbox, ZoneOutbox, ZonePortal}, + abi::{self, TempoState, ZoneFactory, ZoneInbox, ZoneOutbox, ZonePortal}, batch::{ AnchorGapKind, BatchData, BatchSubmitter, ZoneBlockSnapshot, fetch_slot_withdrawals, log_query_ranges, @@ -181,17 +181,29 @@ impl ZoneMonitor { let inbox = ZoneInbox::new(config.inbox_address, provider.clone()); let tempo_state = TempoState::new(config.tempo_state_address, provider.clone()); - let genesis_tempo_block_number: u64 = - ZonePortal::new(config.portal_address, l1_provider.clone()) - .genesisTempoBlockNumber() - .call() - .await - .wrap_err("failed to read genesisTempoBlockNumber during zone monitor startup")?; + let portal_instance = ZonePortal::new(config.portal_address, l1_provider.clone()); + let genesis_tempo_block_number: u64 = portal_instance + .genesisTempoBlockNumber() + .call() + .await + .wrap_err("failed to read genesisTempoBlockNumber during zone monitor startup")?; + + let factory_address: Address = portal_instance + .zoneFactory() + .call() + .await + .wrap_err("failed to read zoneFactory during zone monitor startup")?; + let target_verifier: Address = ZoneFactory::new(factory_address, l1_provider.clone()) + .verifier() + .call() + .await + .wrap_err("failed to read verifier from ZoneFactory during zone monitor startup")?; let batch_submitter = BatchSubmitter::new( config.portal_address, l1_provider, genesis_tempo_block_number, + target_verifier, ); let (prev_zone_block_hash, portal_withdrawal_queue_tail) = tokio::try_join!( @@ -1011,7 +1023,7 @@ mod tests { inbox: ZoneInbox::new(Address::repeat_byte(0x33), zone_provider.clone()), tempo_state: TempoState::new(Address::repeat_byte(0x44), zone_provider), withdrawal_store: SharedWithdrawalStore::new(), - batch_submitter: BatchSubmitter::new(portal_address, l1_provider, 0), + batch_submitter: BatchSubmitter::new(portal_address, l1_provider, 0, Address::ZERO), withdrawal_notify: Arc::new(Notify::new()), repair_notify: Arc::new(Notify::new()), last_submitted_zone_block: 10, diff --git a/crates/tempo-zone/tests/it/utils.rs b/crates/tempo-zone/tests/it/utils.rs index 823146b39..4b0f1413f 100644 --- a/crates/tempo-zone/tests/it/utils.rs +++ b/crates/tempo-zone/tests/it/utils.rs @@ -978,12 +978,10 @@ impl L1TestNode { l1_header.encode(&mut rlp_buf); let genesis_tempo_block_hash = keccak256(&rlp_buf); - let verifier_address = factory.verifier().call().await?; let receipt = factory .createZone(ZoneFactory::CreateZoneParams { token: PATH_USD_ADDRESS, sequencer, - verifier: verifier_address, zoneParams: ZoneFactory::ZoneParams { genesisBlockHash: B256::ZERO, genesisTempoBlockHash: genesis_tempo_block_hash, diff --git a/docs/specs/src/zone/IZone.sol b/docs/specs/src/zone/IZone.sol index 351174645..d062857dd 100644 --- a/docs/specs/src/zone/IZone.sol +++ b/docs/specs/src/zone/IZone.sol @@ -20,7 +20,6 @@ struct ZoneInfo { address messenger; address initialToken; // first TIP-20 enabled at zone creation (additional tokens enabled via enableToken) address sequencer; - address verifier; bytes32 genesisBlockHash; bytes32 genesisTempoBlockHash; uint64 genesisTempoBlockNumber; @@ -420,7 +419,6 @@ interface IZoneFactory { struct CreateZoneParams { address initialToken; // first TIP-20 to enable (sequencer can enable more later) address sequencer; - address verifier; ZoneParams zoneParams; } @@ -430,7 +428,6 @@ interface IZoneFactory { address indexed messenger, address initialToken, address sequencer, - address verifier, bytes32 genesisBlockHash, bytes32 genesisTempoBlockHash, uint64 genesisTempoBlockNumber @@ -441,6 +438,8 @@ interface IZoneFactory { error InvalidVerifier(); error InsufficientGas(); error ZoneIdOverflow(); + error UnknownVerifier(); + error UseForkVerifier(); /// @notice Returns whether a verifier contract is approved for zone creation. /// @param verifier The verifier contract address to check. @@ -448,7 +447,7 @@ interface IZoneFactory { function isValidVerifier(address verifier) external view returns (bool); /// @notice Creates a new zone and deploys its portal and messenger contracts. - /// @param params The initial token, sequencer, verifier, and genesis parameters for the zone. + /// @param params The initial token, sequencer, and genesis parameters for the zone. /// @return zoneId The newly assigned zone ID. /// @return portal The deployed portal address for the new zone. function createZone(CreateZoneParams calldata params) @@ -474,6 +473,26 @@ interface IZoneFactory { /// @return isMessenger True if `messenger` was created by this factory. function isZoneMessenger(address messenger) external view returns (bool); + /// @notice Current active verifier (pre-fork or promoted from previous forkVerifier) + function verifier() external view returns (address); + + /// @notice Post-fork verifier (address(0) if no fork yet) + function forkVerifier() external view returns (address); + + /// @notice L1 block number at which forkVerifier was set (0 = no fork) + function forkActivationBlock() external view returns (uint64); + + /// @notice Protocol version counter, incremented at each hard fork + function protocolVersion() external view returns (uint64); + + /// @notice Rotate verifiers and increment protocolVersion. Called as L1 system transaction. + function setForkVerifier(address newForkVerifier) external; + + /// @notice Validate that targetVerifier is recognized and allowed for the given tempoBlockNumber. + /// @dev Called by portals during submitBatch. Reverts if the verifier is unknown or if + /// the old verifier is used for a batch at or past the fork activation block. + function validateVerifier(address targetVerifier, uint64 tempoBlockNumber) external view; + } /// @notice Per-token configuration in the portal's token registry @@ -585,7 +604,7 @@ interface IZonePortal { function sequencer() external view returns (address); function pendingSequencer() external view returns (address); function zoneGasRate() external view returns (uint128); - function verifier() external view returns (address); + function zoneFactory() external view returns (IZoneFactory); function withdrawalBatchIndex() external view returns (uint64); function blockHash() external view returns (bytes32); function currentDepositQueueHash() external view returns (bytes32); @@ -723,6 +742,7 @@ interface IZonePortal { function processWithdrawal(Withdrawal calldata withdrawal, bytes32 remainingQueue) external; function submitBatch( + address targetVerifier, uint64 tempoBlockNumber, uint64 recentTempoBlockNumber, BlockTransition calldata blockTransition, diff --git a/docs/specs/src/zone/ZoneFactory.sol b/docs/specs/src/zone/ZoneFactory.sol index 474c61c5f..047128d3f 100644 --- a/docs/specs/src/zone/ZoneFactory.sol +++ b/docs/specs/src/zone/ZoneFactory.sol @@ -27,7 +27,18 @@ contract ZoneFactory is IZoneFactory { mapping(address => bool) internal _isZonePortal; mapping(address => bool) internal _isZoneMessenger; mapping(address => bool) internal _validVerifiers; - address internal _verifier; + + /// @notice Current active verifier (pre-fork or promoted from previous forkVerifier) + address public verifier; + + /// @notice Post-fork verifier (address(0) if no fork yet) + address public forkVerifier; + + /// @notice L1 block number at which forkVerifier was set (0 = no fork) + uint64 public forkActivationBlock; + + /// @notice Protocol version counter, incremented at each hard fork + uint64 public protocolVersion; /// @notice Tracks deployment count for CREATE address prediction /// @dev Contracts start with nonce 1, not 0. Nonce 1 is used by the Verifier deployment @@ -41,7 +52,7 @@ contract ZoneFactory is IZoneFactory { constructor() { address v = address(new Verifier()); _validVerifiers[v] = true; - _verifier = v; + verifier = v; } /*////////////////////////////////////////////////////////////// @@ -55,7 +66,6 @@ contract ZoneFactory is IZoneFactory { // Validate initial token is a TIP-20 if (!TempoUtilities.isTIP20(params.initialToken)) revert InvalidToken(); if (params.sequencer == address(0)) revert InvalidSequencer(); - if (!_validVerifiers[params.verifier]) revert InvalidVerifier(); if (gasleft() < ZONE_CREATION_GAS) revert InsufficientGas(); zoneId = _nextZoneId; @@ -89,7 +99,7 @@ contract ZoneFactory is IZoneFactory { params.initialToken, messengerAddress, params.sequencer, - params.verifier, + address(this), params.zoneParams.genesisBlockHash, params.zoneParams.genesisTempoBlockNumber ); @@ -105,7 +115,6 @@ contract ZoneFactory is IZoneFactory { messenger: messengerAddress, initialToken: params.initialToken, sequencer: params.sequencer, - verifier: params.verifier, genesisBlockHash: params.zoneParams.genesisBlockHash, genesisTempoBlockHash: params.zoneParams.genesisTempoBlockHash, genesisTempoBlockNumber: params.zoneParams.genesisTempoBlockNumber @@ -120,7 +129,6 @@ contract ZoneFactory is IZoneFactory { messengerAddress, params.initialToken, params.sequencer, - params.verifier, params.zoneParams.genesisBlockHash, params.zoneParams.genesisTempoBlockHash, params.zoneParams.genesisTempoBlockNumber @@ -155,6 +163,37 @@ contract ZoneFactory is IZoneFactory { return address(uint160(uint256(keccak256(data)))); } + /*////////////////////////////////////////////////////////////// + VERIFIER MANAGEMENT + //////////////////////////////////////////////////////////////*/ + + /// @notice Rotate verifiers and increment protocolVersion. + /// @dev Called as an L1 system transaction at hard fork time. O(1) — no per-zone iteration. + /// On first fork: verifier keeps its value, forkVerifier is set. + /// On subsequent forks: previous forkVerifier promoted to verifier, new one installed. + function setForkVerifier(address newForkVerifier) external { + if (forkVerifier != address(0)) { + verifier = forkVerifier; + } + forkVerifier = newForkVerifier; + forkActivationBlock = uint64(block.number); + _validVerifiers[newForkVerifier] = true; + protocolVersion++; + } + + /// @notice Validate that targetVerifier is recognized and allowed for tempoBlockNumber. + /// @dev Called by portals during submitBatch. + function validateVerifier(address targetVerifier, uint64 tempoBlockNumber) external view { + if (targetVerifier != verifier && targetVerifier != forkVerifier) { + revert UnknownVerifier(); + } + if (targetVerifier == verifier && forkActivationBlock != 0) { + if (tempoBlockNumber >= forkActivationBlock) { + revert UseForkVerifier(); + } + } + } + /*////////////////////////////////////////////////////////////// VIEWS //////////////////////////////////////////////////////////////*/ @@ -180,8 +219,4 @@ contract ZoneFactory is IZoneFactory { return _validVerifiers[v]; } - function verifier() external view returns (address) { - return _verifier; - } - } diff --git a/docs/specs/src/zone/ZonePortal.sol b/docs/specs/src/zone/ZonePortal.sol index bf02c5414..40d9f0d3d 100644 --- a/docs/specs/src/zone/ZonePortal.sol +++ b/docs/specs/src/zone/ZonePortal.sol @@ -18,6 +18,7 @@ import { EncryptedDepositPayload, EncryptionKeyEntry, IVerifier, + IZoneFactory, IZoneMessenger, IZonePortal, QueuedDeposit, @@ -55,7 +56,7 @@ contract ZonePortal is IZonePortal { uint32 public immutable zoneId; address public immutable messenger; - address public immutable verifier; + IZoneFactory public immutable zoneFactory; uint64 public immutable genesisTempoBlockNumber; /// @notice Current sequencer address @@ -102,14 +103,14 @@ contract ZonePortal is IZonePortal { address _initialToken, address _messenger, address _sequencer, - address _verifier, + address _factory, bytes32 _genesisBlockHash, uint64 _genesisTempoBlockNumber ) { zoneId = _zoneId; messenger = _messenger; sequencer = _sequencer; - verifier = _verifier; + zoneFactory = IZoneFactory(_factory); blockHash = _genesisBlockHash; genesisTempoBlockNumber = _genesisTempoBlockNumber; @@ -696,9 +697,11 @@ contract ZonePortal is IZonePortal { //////////////////////////////////////////////////////////////*/ /// @notice Submit a batch and verify the proof. Only callable by the sequencer. + /// @param targetVerifier The verifier to use (must be recognized by the factory) /// @param tempoBlockNumber Block number zone committed to (from zone's TempoState) /// @param recentTempoBlockNumber Optional recent block for ancestry proof (0 = use direct lookup) function submitBatch( + address targetVerifier, uint64 tempoBlockNumber, uint64 recentTempoBlockNumber, BlockTransition calldata blockTransition, @@ -714,6 +717,9 @@ contract ZonePortal is IZonePortal { revert InvalidProof(); } + // Validate verifier is recognized and allowed for this tempoBlockNumber + zoneFactory.validateVerifier(targetVerifier, tempoBlockNumber); + // Validate tempoBlockNumber is valid (applies to both direct and ancestry modes) if (tempoBlockNumber < genesisTempoBlockNumber) revert InvalidTempoBlockNumber(); @@ -739,7 +745,7 @@ contract ZonePortal is IZonePortal { } // Verify proof (handles both direct and ancestry modes) - bool valid = IVerifier(verifier) + bool valid = IVerifier(targetVerifier) .verify( tempoBlockNumber, anchorBlockNumber, diff --git a/docs/specs/test/zone/ZoneBridge.t.sol b/docs/specs/test/zone/ZoneBridge.t.sol index 7fe5506ff..5454a9cef 100644 --- a/docs/specs/test/zone/ZoneBridge.t.sol +++ b/docs/specs/test/zone/ZoneBridge.t.sol @@ -97,6 +97,7 @@ contract ZoneBridgeTest is BaseTest { //////////////////////////////////////////////////////////////*/ MockWithdrawalReceiver public withdrawalReceiver; + address public verifier; uint32 public zoneId; bytes32 constant GENESIS_BLOCK_HASH = keccak256("genesis"); @@ -128,7 +129,8 @@ contract ZoneBridgeTest is BaseTest { super.setUp(); // === Deploy L1 Contracts === - l1Factory = new ZoneFactory(); // Keep factory for verifier only + l1Factory = new ZoneFactory(); + verifier = l1Factory.verifier(); withdrawalReceiver = new MockWithdrawalReceiver(); // Deploy zone token FIRST (used for both L1 escrow and zone-side operations). @@ -155,7 +157,7 @@ contract ZoneBridgeTest is BaseTest { address(l2ZoneToken), // initialToken = MockZoneToken (NOT pathUSD) address(messengerContract), admin, // sequencer - l1Factory.verifier(), + address(l1Factory), GENESIS_BLOCK_HASH, genesisTempoBlockNumber ); @@ -359,6 +361,7 @@ contract ZoneBridgeTest is BaseTest { // Submit to Tempo l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: l1Portal.blockHash(), nextBlockHash: l2BlockHash }), diff --git a/docs/specs/test/zone/ZoneFactory.t.sol b/docs/specs/test/zone/ZoneFactory.t.sol index 852e0fc8d..6c48206bb 100644 --- a/docs/specs/test/zone/ZoneFactory.t.sol +++ b/docs/specs/test/zone/ZoneFactory.t.sol @@ -31,7 +31,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -52,7 +51,6 @@ contract ZoneFactoryTest is BaseTest { assertTrue(info.messenger != address(0)); assertEq(info.initialToken, address(pathUSD)); assertEq(info.sequencer, admin); - assertEq(info.verifier, zoneFactory.verifier()); assertEq(info.genesisBlockHash, GENESIS_BLOCK_HASH); assertEq(info.genesisTempoBlockHash, GENESIS_TEMPO_BLOCK_HASH); } @@ -61,7 +59,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -87,7 +84,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params1 = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -100,7 +96,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params2 = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: alice, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: keccak256("genesis2"), genesisTempoBlockHash: keccak256("tempoGenesis2"), @@ -127,7 +122,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -146,7 +140,7 @@ contract ZoneFactoryTest is BaseTest { if ( logs[i].topics[0] == keccak256( - "ZoneCreated(uint32,address,address,address,address,address,bytes32,bytes32,uint64)" + "ZoneCreated(uint32,address,address,address,address,bytes32,bytes32,uint64)" ) ) { found = true; @@ -171,7 +165,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(0), sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -190,7 +183,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: notTip20, sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -206,7 +198,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: alice, // EOA, not a contract sequencer: admin, - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -226,7 +217,6 @@ contract ZoneFactoryTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: address(0), - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -238,26 +228,6 @@ contract ZoneFactoryTest is BaseTest { zoneFactory.createZone(params); } - /*////////////////////////////////////////////////////////////// - INVALID VERIFIER TESTS - //////////////////////////////////////////////////////////////*/ - - function test_createZone_revertsOnInvalidVerifier() public { - IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ - initialToken: address(pathUSD), - sequencer: admin, - verifier: address(0xdead), - zoneParams: ZoneParams({ - genesisBlockHash: GENESIS_BLOCK_HASH, - genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, - genesisTempoBlockNumber: uint64(block.number) - }) - }); - - vm.expectRevert(IZoneFactory.InvalidVerifier.selector); - zoneFactory.createZone(params); - } - /*////////////////////////////////////////////////////////////// VIEW TESTS //////////////////////////////////////////////////////////////*/ diff --git a/docs/specs/test/zone/ZoneIntegration.t.sol b/docs/specs/test/zone/ZoneIntegration.t.sol index 58ad468ca..ce5e4bb5e 100644 --- a/docs/specs/test/zone/ZoneIntegration.t.sol +++ b/docs/specs/test/zone/ZoneIntegration.t.sol @@ -68,6 +68,7 @@ contract ZoneIntegrationTest is BaseTest { // Helpers TrackingReceiver public receiver; + address public verifier; uint32 public zoneId; bytes32 constant GENESIS_BLOCK_HASH = keccak256("genesis"); @@ -77,7 +78,8 @@ contract ZoneIntegrationTest is BaseTest { function setUp() public override { super.setUp(); - l1Factory = new ZoneFactory(); // Keep for verifier only + l1Factory = new ZoneFactory(); + verifier = l1Factory.verifier(); receiver = new TrackingReceiver(); // Deploy zone token FIRST @@ -101,7 +103,7 @@ contract ZoneIntegrationTest is BaseTest { address(l2ZoneToken), address(messengerContract), admin, - l1Factory.verifier(), + address(l1Factory), GENESIS_BLOCK_HASH, genesisTempoBlockNumber ); @@ -305,6 +307,7 @@ contract ZoneIntegrationTest is BaseTest { // Submit L1 batch for first deposit vm.roll(block.number + 1); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -400,6 +403,7 @@ contract ZoneIntegrationTest is BaseTest { // Submit L1 batch vm.roll(block.number + 1); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -467,6 +471,7 @@ contract ZoneIntegrationTest is BaseTest { bytes32 wHash1 = _finalizeWithdrawalBatch(type(uint256).max); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -492,6 +497,7 @@ contract ZoneIntegrationTest is BaseTest { bytes32 wHash2 = _finalizeWithdrawalBatch(type(uint256).max); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -517,6 +523,7 @@ contract ZoneIntegrationTest is BaseTest { bytes32 wHash3 = _finalizeWithdrawalBatch(type(uint256).max); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -614,6 +621,7 @@ contract ZoneIntegrationTest is BaseTest { // Submit batch with withdrawals vm.roll(block.number + 1); l1Portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ diff --git a/docs/specs/test/zone/ZonePortal.t.sol b/docs/specs/test/zone/ZonePortal.t.sol index 2101ae8ab..55ad70531 100644 --- a/docs/specs/test/zone/ZonePortal.t.sol +++ b/docs/specs/test/zone/ZonePortal.t.sol @@ -131,6 +131,7 @@ contract ZonePortalTest is BaseTest { ZoneFactory public zoneFactory; ZonePortal public portal; ZoneMessenger public messenger; + address public verifier; MockWithdrawalReceiver public withdrawalReceiver; GasConsumingReceiver public gasConsumingReceiver; SuccessfulReceiver public successfulReceiver; @@ -145,6 +146,7 @@ contract ZonePortalTest is BaseTest { // Deploy zone infrastructure zoneFactory = new ZoneFactory(); + verifier = zoneFactory.verifier(); withdrawalReceiver = new MockWithdrawalReceiver(); gasConsumingReceiver = new GasConsumingReceiver(); successfulReceiver = new SuccessfulReceiver(); @@ -164,7 +166,6 @@ contract ZonePortalTest is BaseTest { IZoneFactory.CreateZoneParams memory params = IZoneFactory.CreateZoneParams({ initialToken: address(pathUSD), sequencer: admin, // admin is the sequencer for tests - verifier: zoneFactory.verifier(), zoneParams: ZoneParams({ genesisBlockHash: GENESIS_BLOCK_HASH, genesisTempoBlockHash: GENESIS_TEMPO_BLOCK_HASH, @@ -224,7 +225,7 @@ contract ZonePortalTest is BaseTest { assertEq(portal.zoneId(), testZoneId); assertTrue(portal.isTokenEnabled(address(pathUSD))); assertEq(portal.sequencer(), admin); - assertEq(portal.verifier(), zoneFactory.verifier()); + assertEq(address(portal.zoneFactory()), address(zoneFactory)); assertEq(portal.blockHash(), GENESIS_BLOCK_HASH); assertEq(portal.withdrawalBatchIndex(), 0); assertEq(portal.messenger(), address(messenger)); @@ -411,6 +412,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: newStateRoot }), @@ -434,6 +436,7 @@ contract ZonePortalTest is BaseTest { vm.expectRevert(IZonePortal.InvalidProof.selector); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -457,6 +460,7 @@ contract ZonePortalTest is BaseTest { vm.prank(alice); // Not sequencer vm.expectRevert(IZonePortal.NotSequencer.selector); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: nextStateRoot }), @@ -470,11 +474,7 @@ contract ZonePortalTest is BaseTest { } function test_submitBatch_revertsOnInvalidProof() public { - vm.mockCall( - zoneFactory.verifier(), - abi.encodeWithSelector(IVerifier.verify.selector), - abi.encode(false) - ); + vm.mockCall(verifier, abi.encodeWithSelector(IVerifier.verify.selector), abi.encode(false)); // Advance a block so the history precompile can return a hash vm.roll(block.number + 1); @@ -483,6 +483,7 @@ contract ZonePortalTest is BaseTest { bytes32 nextStateRoot = keccak256("state"); vm.expectRevert(IZonePortal.InvalidProof.selector); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: nextStateRoot }), @@ -519,6 +520,7 @@ contract ZonePortalTest is BaseTest { // Submit batch that adds withdrawal to slot 0 portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -571,6 +573,7 @@ contract ZonePortalTest is BaseTest { // Submit batch adding both withdrawals to slot 0 portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -621,6 +624,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -642,6 +646,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -683,6 +688,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -731,6 +737,7 @@ contract ZonePortalTest is BaseTest { // Submit batch adding withdrawal portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -786,6 +793,7 @@ contract ZonePortalTest is BaseTest { // Submit batch portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -838,6 +846,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -885,6 +894,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -935,6 +945,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -972,6 +983,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -1019,6 +1031,7 @@ contract ZonePortalTest is BaseTest { // Submit batch - portal no longer tracks processed, just updates lastSyncedTempoBlockNumber portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -1051,6 +1064,7 @@ contract ZonePortalTest is BaseTest { bytes32 prevBlockHash = portal.blockHash(); vm.expectRevert(IZonePortal.InvalidTempoBlockNumber.selector); portal.submitBatch( + verifier, genesisTempoBlockNumber - 1, // Before genesis 0, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: keccak256("state") }), @@ -1069,6 +1083,7 @@ contract ZonePortalTest is BaseTest { bytes32 prevBlockHash = portal.blockHash(); vm.expectRevert(IZonePortal.InvalidTempoBlockNumber.selector); portal.submitBatch( + verifier, uint64(block.number + 1), // In future 0, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: keccak256("state") }), @@ -1088,6 +1103,7 @@ contract ZonePortalTest is BaseTest { bytes32 prevBlockHash = portal.blockHash(); vm.expectRevert(IZonePortal.InvalidTempoBlockNumber.selector); portal.submitBatch( + verifier, genesisTempoBlockNumber, // Valid but beyond history window 0, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: keccak256("state") }), @@ -1108,6 +1124,7 @@ contract ZonePortalTest is BaseTest { uint64 recentTempoBlockNumber = uint64(block.number - 1); portal.submitBatch( + verifier, oldTempoBlockNumber, recentTempoBlockNumber, BlockTransition({ @@ -1131,6 +1148,7 @@ contract ZonePortalTest is BaseTest { bytes32 prevBlockHash = portal.blockHash(); vm.expectRevert(IZonePortal.InvalidTempoBlockNumber.selector); portal.submitBatch( + verifier, tempoBlockNumber, tempoBlockNumber, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: keccak256("state") }), @@ -1152,6 +1170,7 @@ contract ZonePortalTest is BaseTest { bytes32 prevBlockHash = portal.blockHash(); vm.expectRevert(IZonePortal.InvalidTempoBlockNumber.selector); portal.submitBatch( + verifier, tempoBlockNumber, futureTempoBlockNumber, BlockTransition({ prevBlockHash: prevBlockHash, nextBlockHash: keccak256("state") }), @@ -1170,6 +1189,7 @@ contract ZonePortalTest is BaseTest { // Should still work at the window boundary portal.submitBatch( + verifier, genesisTempoBlockNumber, 0, BlockTransition({ @@ -1208,6 +1228,7 @@ contract ZonePortalTest is BaseTest { // Even though we pass a "wrong" prevProcessedHash, the implementation // constructs its own from _depositQueue.processed, so this will succeed portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -1239,6 +1260,7 @@ contract ZonePortalTest is BaseTest { // Process first deposit only portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -1256,6 +1278,7 @@ contract ZonePortalTest is BaseTest { // Submit second batch portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ @@ -1285,6 +1308,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1320,6 +1344,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1337,6 +1362,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s2") }), @@ -1387,6 +1413,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1425,6 +1452,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1471,6 +1499,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1517,6 +1546,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1564,6 +1594,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1578,6 +1609,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s2") }), @@ -1592,6 +1624,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s3") }), @@ -1650,6 +1683,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1688,6 +1722,7 @@ contract ZonePortalTest is BaseTest { vm.roll(block.number + 1); portal.submitBatch( + verifier, uint64(block.number - 1), 0, BlockTransition({ prevBlockHash: portal.blockHash(), nextBlockHash: keccak256("s1") }), @@ -1714,7 +1749,7 @@ contract ZonePortalTest is BaseTest { function test_immutableGetters() public view { assertEq(portal.zoneId(), testZoneId); assertEq(portal.sequencer(), admin); - assertEq(portal.verifier(), zoneFactory.verifier()); + assertEq(address(portal.zoneFactory()), address(zoneFactory)); assertEq(portal.genesisTempoBlockNumber(), genesisTempoBlockNumber); } diff --git a/docs/specs/zone_spec.md b/docs/specs/zone_spec.md index 1fe7e2ba3..cba506197 100644 --- a/docs/specs/zone_spec.md +++ b/docs/specs/zone_spec.md @@ -165,9 +165,10 @@ A zone is created via `ZoneFactory.createZone(...)` on Tempo with the following |-----------|-------------| | `initialToken` | The first TIP-20 token to enable. The sequencer can enable additional tokens later. | | `sequencer` | The address that will operate the zone. | -| `verifier` | The `IVerifier` contract used to validate batch proofs. | | `zoneParams` | Genesis configuration: genesis block hash, genesis Tempo block hash, and genesis Tempo block number. | +The verifier is not a per-zone parameter. All zones share the verifier state held on `ZoneFactory` (see [Network Upgrades and Hard Fork Activation](#network-upgrades-and-hard-fork-activation)). + The factory assigns a unique `zoneId`, deploys a [`ZonePortal`](#izoneportal) and a [`ZoneMessenger`](#izonemessenger), and enables the initial token. The [`ZoneCreated`](#izonefactory) event emits all deployment parameters. ### Chain ID @@ -811,6 +812,7 @@ The call takes the following parameters: | Parameter | Description | |-----------|-------------| +| `targetVerifier` | The `IVerifier` contract to validate this batch. Must be accepted by `ZoneFactory.validateVerifier(targetVerifier, tempoBlockNumber)`. | | `tempoBlockNumber` | The Tempo block the zone committed to via `TempoState` | | `recentTempoBlockNumber` | A recent Tempo block for ancestry validation (`0` for direct lookup) | | `blockTransition` | Zone block hash transition: `prevBlockHash` to `nextBlockHash` | @@ -819,6 +821,8 @@ The call takes the following parameters: | `verifierConfig` | Opaque payload for the verifier (domain separation, attestation data) | | `proof` | The proof or attestation produced by the proving backend | +The portal delegates verifier selection to `ZoneFactory`. It calls `zoneFactory().validateVerifier(targetVerifier, tempoBlockNumber)`, which reverts if `targetVerifier` is unknown or if the old verifier is used for a batch at or past the fork activation block. On success, the portal invokes `IVerifier(targetVerifier).verify(...)`. + On success, the portal: 1. Updates `blockHash` to `nextBlockHash`. @@ -1012,7 +1016,6 @@ struct ZoneInfo { address messenger; address initialToken; address sequencer; - address verifier; bytes32 genesisBlockHash; bytes32 genesisTempoBlockHash; uint64 genesisTempoBlockNumber; @@ -1037,13 +1040,12 @@ interface IZoneFactory { struct CreateZoneParams { address initialToken; address sequencer; - address verifier; ZoneParams zoneParams; } event ZoneCreated( uint32 indexed zoneId, address indexed portal, address indexed messenger, - address initialToken, address sequencer, address verifier, + address initialToken, address sequencer, bytes32 genesisBlockHash, bytes32 genesisTempoBlockHash, uint64 genesisTempoBlockNumber ); @@ -1051,6 +1053,21 @@ interface IZoneFactory { function zoneCount() external view returns (uint32); function zones(uint32 zoneId) external view returns (ZoneInfo memory); function isZonePortal(address portal) external view returns (bool); + function isZoneMessenger(address messenger) external view returns (bool); + function isValidVerifier(address verifier) external view returns (bool); + + // Centralized verifier state (shared across all zones) + function verifier() external view returns (address); + function forkVerifier() external view returns (address); + function forkActivationBlock() external view returns (uint64); + function protocolVersion() external view returns (uint64); + + // Hard fork rotation (called as a Tempo L1 system transaction) + function setForkVerifier(address newForkVerifier) external; + + // Called by portals during submitBatch. Reverts if targetVerifier is + // unknown or if the old verifier is used at or past the fork block. + function validateVerifier(address targetVerifier, uint64 tempoBlockNumber) external view; } ``` @@ -1104,6 +1121,7 @@ interface IZonePortal { // Batch submission function submitBatch( + address targetVerifier, uint64 tempoBlockNumber, uint64 recentTempoBlockNumber, BlockTransition calldata blockTransition, DepositQueueTransition calldata depositQueueTransition, bytes32 withdrawalQueueHash, bytes calldata verifierConfig, bytes calldata proof @@ -1124,7 +1142,7 @@ interface IZonePortal { // State function sequencer() external view returns (address); - function verifier() external view returns (address); + function zoneFactory() external view returns (IZoneFactory); function blockHash() external view returns (bytes32); function currentDepositQueueHash() external view returns (bytes32); function withdrawalBatchIndex() external view returns (uint64); @@ -1266,23 +1284,29 @@ Deployed at the same address as on Tempo. Read-only on the zone. Its `isAuthoriz ## Network Upgrades and Hard Fork Activation -> **Note:** The verifier rotation and protocol version mechanisms described below are the target design. The current `ZonePortal` implementation declares `verifier` as `immutable`, so the rotation mechanism is not yet implemented. This section will be updated when the upgrade contracts are deployed. - Zones activate hard fork upgrades in lockstep with Tempo using same-block activation. The trigger is the Tempo block number: the zone block whose `advanceTempo` imports the fork Tempo block uses the new execution rules for its entire scope. -The portal will maintain two verifier slots (`verifier` and `forkVerifier`). At each fork, verifiers rotate: the previous fork verifier is promoted to `verifier`, and the new fork verifier takes the `forkVerifier` slot. At most two verifiers are active at any time. The `IVerifier` interface is unchanged across forks. New proof parameters are passed via the opaque `verifierConfig` bytes. +Verifier state is centralized on `ZoneFactory` and shared by every zone portal. The factory maintains two verifier slots (`verifier` and `forkVerifier`), a `forkActivationBlock`, and a monotonically increasing `protocolVersion` counter. At each fork, verifiers rotate on the factory: the previous `forkVerifier` is promoted to `verifier`, and the new fork verifier takes the `forkVerifier` slot. At most two verifiers are active at any time. The `IVerifier` interface is unchanged across forks; new proof parameters are passed via the opaque `verifierConfig` bytes. + +Zone nodes embed the highest `protocolVersion` they support and halt cleanly if the imported Tempo block bumps `protocolVersion` beyond their supported version, preventing an outdated node from producing blocks under incorrect rules. + +No onchain action is required from zone operators or from individual portals. The new verifier is deployed and rotated on the factory as part of the Tempo hard fork. Operators upgrade their zone node binary and prover program before the fork. When the fork Tempo block arrives, the node activates new rules automatically. + +### Portal verifier routing + +`submitBatch` takes an explicit `targetVerifier` parameter. Before verification, the portal calls `ZoneFactory.validateVerifier(targetVerifier, tempoBlockNumber)`, which enforces: -`ZoneFactory` will maintain a `protocolVersion` counter incremented at each fork. Zone nodes embed the highest protocol version they support and halt cleanly if the imported Tempo block bumps `protocolVersion` beyond their supported version, preventing an outdated node from producing blocks under incorrect rules. +1. `targetVerifier` is either the current `verifier` or the current `forkVerifier` (reverts with `UnknownVerifier` otherwise). +2. If `targetVerifier` is the old `verifier` and `forkVerifier` is set, then `tempoBlockNumber < forkActivationBlock` (reverts with `UseForkVerifier` otherwise). -No onchain action is required from zone operators. The new verifier is deployed and rotated as part of the Tempo hard fork. Operators upgrade their zone node binary and prover program before the fork. When the fork Tempo block arrives, the node activates new rules automatically. +On success the portal invokes `IVerifier(targetVerifier).verify(...)`. Because state is centralized, a hard fork requires no per-portal iteration: a single `setForkVerifier` call on the factory rotates verifiers for every zone simultaneously (O(1) instead of O(n)). -The portal will enforce a `forkActivationBlock` cutoff where batches targeting the old `verifier` must have `tempoBlockNumber < forkActivationBlock`. This prevents post-fork batches from being submitted against old verification rules. +### Fork block system transactions The Tempo hard fork block executes the following as system transactions: 1. Deploy the new `IVerifier` contract. -2. Call `ZoneFactory.setForkVerifier(forkVerifier)`, which for each registered portal promotes `forkVerifier` to `verifier`, installs the new verifier as `forkVerifier`, and sets `forkActivationBlock = block.number`. -3. Increment `ZoneFactory.protocolVersion`. +2. Call `ZoneFactory.setForkVerifier(newForkVerifier)`, which promotes the previous `forkVerifier` to `verifier`, installs `newForkVerifier` as `forkVerifier`, sets `forkActivationBlock = block.number`, and increments `protocolVersion`. If the fork changes zone predeploy behavior, the zone node injects new bytecode at the predeploy addresses before `advanceTempo` executes in the first post-fork zone block. diff --git a/xtask/src/zone_info.rs b/xtask/src/zone_info.rs index 95a19cf86..f8b124d47 100644 --- a/xtask/src/zone_info.rs +++ b/xtask/src/zone_info.rs @@ -58,7 +58,6 @@ impl ZoneInfoCmd { println!(" Messenger: {}", info.messenger); println!(" Initial Token: {}", info.initialToken); println!(" Sequencer: {}", info.sequencer); - println!(" Verifier: {}", info.verifier); println!(" Genesis Block Hash: {}", info.genesisBlockHash); println!(" Genesis Tempo Hash: {}", info.genesisTempoBlockHash); println!(" Genesis Tempo Block: {}", info.genesisTempoBlockNumber);