Skip to content

Commit c48ee8a

Browse files
tomatoishealthyallen.wuSegueIIpanos-xyzKukoomomo
authored
Feature: support sequencer rotate (#867)
Co-authored-by: allen.wu <[email protected]> Co-authored-by: Segue <[email protected]> Co-authored-by: panos <[email protected]> Co-authored-by: panos <[email protected]> Co-authored-by: kukoo <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent bec312a commit c48ee8a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+6790
-567
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
################## update dependencies ####################
2-
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := morph-v2.1.2
3-
ETHEREUM_TARGET_VERSION := morph-v2.1.2
4-
TENDERMINT_TARGET_VERSION := v0.3.3
2+
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := test_3_13
3+
ETHEREUM_TARGET_VERSION := v1.10.14-0.20260303114154-29281e501802
4+
TENDERMINT_TARGET_VERSION := v0.3.4-0.20260226093240-9be76fe518c2
55

66

77
ETHEREUM_MODULE_NAME := github.com/morph-l2/go-ethereum

bindings/bindings/l1sequencer.go

Lines changed: 820 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ module morph-l2/bindings
22

33
go 1.24.0
44

5-
replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3
5+
replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260313040448-999449fd4d23
66

7-
require github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141
7+
require github.com/morph-l2/go-ethereum v1.10.14-0.20260312125309-280bfb9cfd1d
88

99
require (
1010
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect

bindings/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky
111111
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
112112
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
113113
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
114-
github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ=
115-
github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
114+
github.com/morph-l2/go-ethereum v1.10.14-0.20260312125309-280bfb9cfd1d h1:Qy3ytYw/PGnrPDAWen1MsMUhUXclk1F2Q36A07+bBv4=
115+
github.com/morph-l2/go-ethereum v1.10.14-0.20260312125309-280bfb9cfd1d/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
116116
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
117117
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
118118
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity =0.8.24;
3+
4+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
5+
6+
/// @title L1Sequencer
7+
/// @notice L1 contract for managing the sequencer address.
8+
/// The sequencer address can be updated by the owner (multisig recommended).
9+
contract L1Sequencer is OwnableUpgradeable {
10+
// ============ Storage ============
11+
12+
/// @notice Current sequencer address
13+
address public sequencer;
14+
15+
// ============ Events ============
16+
17+
/// @notice Emitted when sequencer is updated
18+
event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer);
19+
20+
// ============ Initializer ============
21+
22+
/// @notice Initialize the contract
23+
/// @param _owner Contract owner (multisig recommended)
24+
/// @param _initialSequencer Initial sequencer address (can be address(0) to set later)
25+
function initialize(address _owner, address _initialSequencer) external initializer {
26+
require(_owner != address(0), "invalid owner");
27+
28+
__Ownable_init();
29+
_transferOwnership(_owner);
30+
31+
// Set initial sequencer if provided
32+
if (_initialSequencer != address(0)) {
33+
sequencer = _initialSequencer;
34+
emit SequencerUpdated(address(0), _initialSequencer);
35+
}
36+
}
37+
38+
// ============ Admin Functions ============
39+
40+
/// @notice Update sequencer address (takes effect immediately)
41+
/// @param newSequencer New sequencer address
42+
function updateSequencer(address newSequencer) external onlyOwner {
43+
require(newSequencer != address(0), "invalid sequencer");
44+
require(newSequencer != sequencer, "same sequencer");
45+
46+
address oldSequencer = sequencer;
47+
sequencer = newSequencer;
48+
49+
emit SequencerUpdated(oldSequencer, newSequencer);
50+
}
51+
52+
// ============ View Functions ============
53+
54+
/// @notice Get current sequencer address
55+
function getSequencer() external view returns (address) {
56+
return sequencer;
57+
}
58+
}

contracts/deploy/013-DeployProxys.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const deployContractProxies = async (
4848

4949
const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName
5050
const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName
51+
const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName
5152

5253
const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName
5354
const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName
@@ -112,6 +113,13 @@ export const deployContractProxies = async (
112113
return err
113114
}
114115

116+
// ************************ sequencer contracts deploy ************************
117+
// L1SequencerProxy deploy
118+
err = await deployContractProxyByStorageName(hre, path, deployer, L1SequencerProxyStorageName)
119+
if (err != "") {
120+
return err
121+
}
122+
115123
// ************************ rollup contracts deploy ************************
116124
// RollupProxy deploy
117125
err = await deployContractProxyByStorageName(hre, path, deployer, RollupProxyStorageName)
@@ -274,6 +282,7 @@ export const deployContractProxiesConcurrently = async (
274282
ProxyStorageName.L1CrossDomainMessengerProxyStorageName,
275283
ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName,
276284
ProxyStorageName.L1StakingProxyStorageName,
285+
ProxyStorageName.L1SequencerProxyStorageName,
277286
ProxyStorageName.RollupProxyStorageName,
278287
ProxyStorageName.L1GatewayRouterProxyStorageName,
279288
ProxyStorageName.L1ETHGatewayProxyStorageName,

contracts/deploy/014-DeployImpls.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ export const deployContractImplsConcurrently = async (
122122

123123
deployPromises.push(deployContract(L1StakingFactoryName, StakingImplStorageName, [L1CrossDomainMessengerProxyAddress]))
124124

125+
// L1Sequencer deploy (no constructor args)
126+
const L1SequencerFactoryName = ContractFactoryName.L1Sequencer
127+
const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName
128+
deployPromises.push(deployContract(L1SequencerFactoryName, L1SequencerImplStorageName))
129+
125130
const results = await Promise.all(deployPromises)
126131

127132
for (const result of results) {
@@ -382,6 +387,21 @@ export const deployContractImpls = async (
382387
return err
383388
}
384389

390+
// ************************ sequencer contracts deploy ************************
391+
// L1Sequencer deploy
392+
const L1SequencerFactoryName = ContractFactoryName.L1Sequencer
393+
const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName
394+
Factory = await hre.ethers.getContractFactory(L1SequencerFactoryName)
395+
contract = await Factory.deploy()
396+
await contract.deployed()
397+
console.log("%s=%s ; TX_HASH: %s", L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), contract.deployTransaction.hash)
398+
blockNumber = await hre.ethers.provider.getBlockNumber()
399+
console.log("BLOCK_NUMBER: %s", blockNumber)
400+
err = await storage(path, L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), blockNumber || 0)
401+
if (err != '') {
402+
return err
403+
}
404+
385405
// return
386406
return ''
387407
}

contracts/deploy/019-AdminTransfer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export const AdminTransferConcurrently = async (
109109
ProxyStorageName.L1CrossDomainMessengerProxyStorageName,
110110
ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName,
111111
ProxyStorageName.L1StakingProxyStorageName,
112+
ProxyStorageName.L1SequencerProxyStorageName, // Added L1Sequencer
112113
ProxyStorageName.RollupProxyStorageName,
113114
ProxyStorageName.L1GatewayRouterProxyStorageName,
114115
ProxyStorageName.L1ETHGatewayProxyStorageName,
@@ -159,6 +160,7 @@ export const AdminTransfer = async (
159160

160161
const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName
161162
const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName
163+
const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName
162164

163165
const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName
164166
const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName
@@ -192,6 +194,13 @@ export const AdminTransfer = async (
192194
return err
193195
}
194196

197+
// ************************ sequencer contracts admin change ************************
198+
// L1SequencerProxy admin change
199+
err = await AdminTransferByProxyStorageName(hre, path, deployer, L1SequencerProxyStorageName)
200+
if (err != '') {
201+
return err
202+
}
203+
195204
// ************************ rollup contracts admin change ************************
196205
// RollupProxy admin change
197206
err = await AdminTransferByProxyStorageName(hre, path, deployer, RollupProxyStorageName)

contracts/deploy/020-ContractInit.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,32 @@ export const ContractInit = async (
5757
// submitter and challenger
5858
const submitter: string = config.rollupProposer
5959
const challenger: string = config.rollupChallenger
60+
const rollupDelayPeriod: number = config.rollupDelayPeriod
61+
6062
if (!ethers.utils.isAddress(submitter)
6163
|| !ethers.utils.isAddress(challenger)
6264
) {
6365
console.error('please check your address')
6466
return ''
6567
}
68+
if (rollupDelayPeriod==0){
69+
console.error('rollupDelayPeriod cannot set zero')
70+
return ''
71+
}
6672
let res = await Rollup.importGenesisBatch(batchHeader)
6773
let rec = await res.wait()
6874
console.log(`importGenesisBatch(%s) ${rec.status == 1 ? "success" : "failed"}`, batchHeader)
6975
res = await Rollup.addChallenger(challenger)
7076
rec = await res.wait()
7177
console.log(`addChallenger(%s) ${rec.status == 1 ? "success" : "failed"}`, challenger)
78+
79+
res =await Rollup.initialize2("0x0000000000000000000000000000000000000000000000000000000000000001")
80+
rec = await res.wait()
81+
console.log(`initialize2(%s) ${rec.status == 1 ? "success" : "failed"}`)
82+
83+
res = await Rollup.initialize3(rollupDelayPeriod)
84+
rec = await res.wait()
85+
console.log(`initialize3(%s) ${rec.status == 1 ? "success" : "failed"}`)
7286
}
7387

7488
// ------------------ staking init -----------------
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import "@nomiclabs/hardhat-web3";
2+
import "@nomiclabs/hardhat-ethers";
3+
import "@nomiclabs/hardhat-waffle";
4+
5+
import {
6+
HardhatRuntimeEnvironment
7+
} from 'hardhat/types';
8+
import { assertContractVariable, getContractAddressByName, awaitCondition } from "../src/deploy-utils";
9+
import { ethers } from 'ethers'
10+
11+
import {
12+
ImplStorageName,
13+
ProxyStorageName,
14+
ContractFactoryName,
15+
} from "../src/types"
16+
17+
export const SequencerInit = async (
18+
hre: HardhatRuntimeEnvironment,
19+
path: string,
20+
deployer: any,
21+
configTmp: any
22+
): Promise<string> => {
23+
// L1Sequencer addresses
24+
const L1SequencerProxyAddress = getContractAddressByName(path, ProxyStorageName.L1SequencerProxyStorageName)
25+
const L1SequencerImplAddress = getContractAddressByName(path, ImplStorageName.L1SequencerStorageName)
26+
const L1SequencerFactory = await hre.ethers.getContractFactory(ContractFactoryName.L1Sequencer)
27+
28+
const IL1SequencerProxy = await hre.ethers.getContractAt(ContractFactoryName.DefaultProxyInterface, L1SequencerProxyAddress, deployer)
29+
30+
if (
31+
(await IL1SequencerProxy.implementation()).toLocaleLowerCase() !== L1SequencerImplAddress.toLocaleLowerCase()
32+
) {
33+
console.log('Upgrading the L1Sequencer proxy...')
34+
35+
// Owner is the deployer (will be transferred to multisig in production)
36+
const owner = await deployer.getAddress()
37+
38+
// Get initial sequencer address from config (first sequencer address)
39+
// Note: l2SequencerAddresses is defined in contracts/src/deploy-config/l1.ts
40+
const initialSequencer = (configTmp.l2SequencerAddresses && configTmp.l2SequencerAddresses.length > 0)
41+
? configTmp.l2SequencerAddresses[0]
42+
: ethers.constants.AddressZero
43+
44+
console.log('Initial sequencer address:', initialSequencer)
45+
46+
// Upgrade and initialize the proxy with owner and initial sequencer
47+
// Note: We set sequencer in initialize() to avoid TransparentUpgradeableProxy admin restriction
48+
await IL1SequencerProxy.upgradeToAndCall(
49+
L1SequencerImplAddress,
50+
L1SequencerFactory.interface.encodeFunctionData('initialize', [owner, initialSequencer])
51+
)
52+
53+
await awaitCondition(
54+
async () => {
55+
return (
56+
(await IL1SequencerProxy.implementation()).toLocaleLowerCase() === L1SequencerImplAddress.toLocaleLowerCase()
57+
)
58+
},
59+
3000,
60+
1000
61+
)
62+
63+
const contractTmp = new ethers.Contract(
64+
L1SequencerProxyAddress,
65+
L1SequencerFactory.interface,
66+
deployer,
67+
)
68+
69+
await assertContractVariable(
70+
contractTmp,
71+
'owner',
72+
owner,
73+
)
74+
75+
if (initialSequencer !== ethers.constants.AddressZero) {
76+
await assertContractVariable(
77+
contractTmp,
78+
'sequencer',
79+
initialSequencer,
80+
)
81+
console.log('L1SequencerProxy upgrade success, initial sequencer set:', initialSequencer)
82+
} else {
83+
console.log('L1SequencerProxy upgrade success (no initial sequencer set)')
84+
}
85+
}
86+
return ''
87+
}
88+
89+
export default SequencerInit

0 commit comments

Comments
 (0)