Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0e35430
feat: L1Sequencer history + SequencerVerifier + deploy fix
Mar 26, 2026
8b14331
feat: P2P security test framework (build-malicious + p2p-test)
Mar 26, 2026
33cb79e
fix: p2p security test script improvements
Mar 26, 2026
a6e46f3
fix: sequencer key + L1 initializeHistory in devnet setup
Mar 27, 2026
311338c
fix: T-06 blocksync main path + Phase 0 precondition checks
Mar 27, 2026
66a6873
feat: L1Sequencer unit tests + bindings regen + verifier retry backoff
Mar 27, 2026
b3f75c5
refactor: rename Solidity struct SequencerRecord -> HistoryRecord
Mar 27, 2026
079fd08
refactor: remove CONSENSUS_SWITCH_HEIGHT flag, read upgrade height fr…
Mar 30, 2026
79e1289
revert: restore docker-compose-4nodes.yml to main state
Apr 16, 2026
9d0ee5d
fix: swap ServerSuffrage iota so Nonvoter=0 (safer zero value)
Apr 8, 2026
52c98d8
feat: Sequencer HA V2 — Raft-based high availability for single seque…
Apr 9, 2026
b71f990
feat: add NewL2BlockV2 support in executor + RetryableClient
Apr 16, 2026
4a1fc54
feat: mark block hash and NextL1MsgIndex errors as non-retryable
Apr 17, 2026
be2f3a7
fix: ensure L1 syncer is initialized for post-upgrade sequencer nodes…
Apr 28, 2026
01f1cb0
feature: optimize ha test script & docker file
Apr 30, 2026
955cd64
fix: harden BlockFSM.Apply and increase blockCh buffer
May 7, 2026
0ddf1e7
chore: bump go-ethereum and tendermint to feat/sequencer-optimize branch
May 8, 2026
50e63cf
chore: pin go-ethereum to 56deb7072 across all modules
May 9, 2026
1ea0937
deps: bump tendermint to v0.0.0-20260515043308-c6f7e21e4b14
May 15, 2026
cc392b7
feat: update docker test file
May 28, 2026
6f5c564
feat(derivation): SPEC-005 verify-mode + Path B local rebuild
May 28, 2026
611f390
refactor(node): drop validator role + blocktag service
May 28, 2026
b6eae18
fix(node): sync idempotency + retryable client + db helpers
May 28, 2026
bf84c4b
refactor(node): consolidate L1 client + integrate derivation reorg
May 29, 2026
ee030aa
refactor(node): consolidate safe-write API to NewSafeL2Block + bump deps
May 29, 2026
5ec621b
feat(derivation): deriveForce skip (#967)
curryxbo Jun 2, 2026
4dc622f
feat: add EnclaveSigner client for Nitro Enclave block signing
May 14, 2026
f3200ab
feat(l1sequencer): accept vsock:CID:port in EnclaveSigner addr
May 20, 2026
d81be99
refactor(node): skip derivation startup in sequencer mode
Jun 2, 2026
ef4f09b
deps: bump tendermint and go-ethereum to feat/sequencer-final HEAD
Jun 2, 2026
1f5a792
feat(genesis): emit v2 (257-byte) batch header at devnet bootstrap
Jun 2, 2026
9524b7e
feat: add --mainnet/--hoodi network presets and bump go-ethereum (#973)
tomatoishealthy Jun 4, 2026
16b2359
feat(node): layer1 verify-mode skips tendermint + own metrics server …
curryxbo Jun 4, 2026
ba8d358
feat(derivation): skip L1 fill-gap derivation for pre-upgrade blocks …
tomatoishealthy Jun 8, 2026
4a7b911
fix(derivation): clamp deriveForce skipNumber to batch tip (#983)
curryxbo Jun 9, 2026
d7b9f52
fix(node): golangci-lint failures + broken test Dockerfile/script (#984)
tomatoishealthy Jun 10, 2026
7093489
fix(l1sequencer): fail-fast on verifier startup when L1 unreachable (…
chengwenxi Jun 10, 2026
43892bd
tmp: skip upgrade height check for test
Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
################## update dependencies ####################
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := morph-v2.2.3
ETHEREUM_TARGET_VERSION := morph-v2.2.3
TENDERMINT_TARGET_VERSION := v0.3.7
ETHEREUM_SUBMODULE_COMMIT_OR_TAG := 56deb7072ae467a12a850815c7a5c09b7c2782ba
ETHEREUM_TARGET_VERSION := v0.0.0-20260508105911-56deb7072ae4
TENDERMINT_TARGET_VERSION := v0.0.0-20260515043308-c6f7e21e4b14


ETHEREUM_MODULE_NAME := github.com/morph-l2/go-ethereum
Expand Down
236 changes: 201 additions & 35 deletions bindings/bindings/l1sequencer.go

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions bindings/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ module morph-l2/bindings

go 1.24.0

replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.7
replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a

require github.com/morph-l2/go-ethereum v1.10.14-0.20260529032230-76b52dff186f
replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc

require github.com/morph-l2/go-ethereum v1.10.14-0.20251219060125-03910bc750a2

require (
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
Expand Down
4 changes: 2 additions & 2 deletions bindings/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morph-l2/go-ethereum v1.10.14-0.20260529032230-76b52dff186f h1:qpeu1j1GLb59mhm5UW75YE7NYv5e+lFyShYzbAvLbtk=
github.com/morph-l2/go-ethereum v1.10.14-0.20260529032230-76b52dff186f/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc h1:2Umr8WRDBKwCgGrQQ8yCdhn71bCuMJuecId2ClK80DU=
github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down
21 changes: 15 additions & 6 deletions common/batch/batch_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ func (bc *BatchCache) CalculateCapWithProposalBlock(blockNumber uint64, withdraw
}

// Parse transactions, distinguish L1 and L2 transactions
txsPayload, l1TxHashes, newTotalL1MessagePopped, l2TxNum, err := parsingTxs(block.Transactions(), bc.totalL1MessagePopped)
txsPayload, l1TxHashes, newTotalL1MessagePopped, l2TxNum, err := ParsingTxs(block.Transactions(), bc.totalL1MessagePopped)
if err != nil {
return false, fmt.Errorf("failed to parse transactions: %w", err)
}
Expand All @@ -530,7 +530,7 @@ func (bc *BatchCache) CalculateCapWithProposalBlock(blockNumber uint64, withdraw
txsNum := l2TxNum + l1TxNum

// Build BlockContext (60 bytes)
blockContext := buildBlockContext(header, txsNum, l1TxNum)
blockContext := BuildBlockContext(header, txsNum, l1TxNum)

// Store to current, do not immediately append to batch
bc.currentBlockContext = blockContext
Expand Down Expand Up @@ -934,8 +934,14 @@ func (bc *BatchCache) createBatchHeader(dataHash common.Hash, sidecar *ethtypes.
return batchHeaderV0.Bytes()
}

// parsingTxs parses transactions, distinguishes L1 and L2 transactions
func parsingTxs(transactions []*ethtypes.Transaction, totalL1MessagePoppedBefore uint64) (
// ParsingTxs encodes a block's transactions into the on-chain payload format
// used by the batch builder: L2 transactions are RLP-marshalled and concatenated
// in order; L1 message transactions are excluded from the payload but their
// hashes and queue indices are tracked separately.
//
// Exported for derivation local verify (SPEC-005), which must rebuild blob bytes from
// local L2 blocks using the same encoding the sequencer applied at seal time.
func ParsingTxs(transactions []*ethtypes.Transaction, totalL1MessagePoppedBefore uint64) (
txsPayload []byte,
l1TxHashes []common.Hash,
totalL1MessagePopped uint64,
Expand Down Expand Up @@ -1010,9 +1016,12 @@ func (bc *BatchCache) sealEffectiveBlobCount(blockTimestamp uint64, replayCommit
return replayProtocolMaxBlobs
}

// buildBlockContext builds BlockContext from block header (60 bytes)
// BuildBlockContext serialises a block header + tx counts into the 60-byte
// BlockContext blob the batch builder writes for each block.
// Format: Number(8) || Timestamp(8) || BaseFee(32) || GasLimit(8) || numTxs(2) || numL1Messages(2)
func buildBlockContext(header *ethtypes.Header, txsNum, l1MsgNum int) []byte {
//
// Exported for derivation local verify (SPEC-005); see ParsingTxs.
func BuildBlockContext(header *ethtypes.Header, txsNum, l1MsgNum int) []byte {
blsBytes := make([]byte, 60)

// Number (8 bytes)
Expand Down
6 changes: 4 additions & 2 deletions common/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ module morph-l2/common

go 1.24.0

replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.7
replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a

replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc

require (
github.com/holiman/uint256 v1.2.4
github.com/morph-l2/go-ethereum v1.10.14-0.20260529032230-76b52dff186f
github.com/morph-l2/go-ethereum v1.10.14-0.20251219060125-03910bc750a2
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a
)
Expand Down
6 changes: 5 additions & 1 deletion common/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8x
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
Expand Down Expand Up @@ -145,7 +148,8 @@ github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqky
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morph-l2/go-ethereum v1.10.14-0.20260529032230-76b52dff186f h1:qpeu1j1GLb59mhm5UW75YE7NYv5e+lFyShYzbAvLbtk=
github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc h1:2Umr8WRDBKwCgGrQQ8yCdhn71bCuMJuecId2ClK80DU=
github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
Expand Down
129 changes: 104 additions & 25 deletions contracts/contracts/l1/L1Sequencer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,134 @@ pragma solidity =0.8.24;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

/// @title L1Sequencer
/// @notice L1 contract for managing the sequencer address.
/// The sequencer address can be updated by the owner (multisig recommended).
/// @notice L1 contract for managing sequencer address with history tracking.
/// Supports querying which sequencer was active at any given L2 block height.
contract L1Sequencer is OwnableUpgradeable {
// ============ Types ============

struct HistoryRecord {
uint64 startL2Block;
address sequencerAddr;
}

// ============ Storage ============

/// @notice Current sequencer address
address public sequencer;
/// @notice Ordered array of sequencer records (by startL2Block ascending).
/// sequencerHistory[0] is the first sequencer after PBFT → single-sequencer upgrade.
HistoryRecord[] public sequencerHistory;

/// @notice The L2 block height at which single-sequencer mode activates.
/// Set by initializeHistory(). Nodes read this to know when to switch consensus.
uint64 public activeHeight;

// ============ Events ============

/// @notice Emitted when sequencer is updated
event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer);
event SequencerUpdated(
address indexed oldSequencer,
address indexed newSequencer,
uint64 startL2Block
);

// ============ Initializer ============

/// @notice Initialize the contract
/// @param _owner Contract owner (multisig recommended)
/// @param _initialSequencer Initial sequencer address (can be address(0) to set later)
function initialize(address _owner, address _initialSequencer) external initializer {
function initialize(address _owner) external initializer {
require(_owner != address(0), "invalid owner");

__Ownable_init();
_transferOwnership(_owner);

// Set initial sequencer if provided
if (_initialSequencer != address(0)) {
sequencer = _initialSequencer;
emit SequencerUpdated(address(0), _initialSequencer);
}
}

// ============ Admin Functions ============

/// @notice Update sequencer address (takes effect immediately)
/// @param newSequencer New sequencer address
function updateSequencer(address newSequencer) external onlyOwner {
require(newSequencer != address(0), "invalid sequencer");
require(newSequencer != sequencer, "same sequencer");
/// @notice Initialize sequencer history (called once before the L2 upgrade).
/// @param firstSequencer The first sequencer address after the upgrade.
/// @param upgradeL2Block The L2 block height where single-sequencer mode activates.
function initializeHistory(
address firstSequencer,
uint64 upgradeL2Block
) external onlyOwner {
require(sequencerHistory.length == 0, "already initialized");
require(firstSequencer != address(0), "invalid address");

sequencerHistory.push(HistoryRecord({
startL2Block: upgradeL2Block,
sequencerAddr: firstSequencer
}));
activeHeight = upgradeL2Block;

emit SequencerUpdated(address(0), firstSequencer, upgradeL2Block);
}

/// @notice Register a sequencer change at a future L2 block height.
/// The new sequencer is NOT active until startL2Block is reached.
/// @param newSequencer New sequencer address.
/// @param startL2Block L2 block height when the new sequencer takes over.
/// Must be strictly greater than the last record.
function updateSequencer(
address newSequencer,
uint64 startL2Block
) external onlyOwner {
require(newSequencer != address(0), "invalid address");
require(sequencerHistory.length > 0, "not initialized");
require(
startL2Block > sequencerHistory[sequencerHistory.length - 1].startL2Block,
"startL2Block must be greater than last record"
);

address oldSequencer = sequencer;
sequencer = newSequencer;
address oldSequencer = sequencerHistory[sequencerHistory.length - 1].sequencerAddr;

emit SequencerUpdated(oldSequencer, newSequencer);
sequencerHistory.push(HistoryRecord({
startL2Block: startL2Block,
sequencerAddr: newSequencer
}));

emit SequencerUpdated(oldSequencer, newSequencer, startL2Block);
}

// ============ View Functions ============

/// @notice Get current sequencer address
/// @notice Get the sequencer that was active at a given L2 block height.
/// @dev Binary search: O(log n).
function getSequencerAt(uint64 l2Height) external view returns (address) {
uint256 len = sequencerHistory.length;
require(len > 0, "no sequencer configured");

uint256 low = 0;
uint256 high = len - 1;
uint256 result = 0;

while (low <= high) {
uint256 mid = (low + high) / 2;
if (sequencerHistory[mid].startL2Block <= l2Height) {
result = mid;
if (mid == high) break;
low = mid + 1;
} else {
if (mid == 0) break;
high = mid - 1;
}
}

require(sequencerHistory[result].startL2Block <= l2Height, "no sequencer at height");
return sequencerHistory[result].sequencerAddr;
}

/// @notice Get the latest registered sequencer address (backward compat).
/// @dev If the latest record's startL2Block hasn't been reached yet,
/// this address is scheduled but not yet active.
function getSequencer() external view returns (address) {
return sequencer;
require(sequencerHistory.length > 0, "no sequencer configured");
return sequencerHistory[sequencerHistory.length - 1].sequencerAddr;
}

/// @notice Get the full sequencer history (for L2 node bulk sync at startup).
function getSequencerHistory() external view returns (HistoryRecord[] memory) {
return sequencerHistory;
}

/// @notice Get the number of sequencer history records.
function getSequencerHistoryLength() external view returns (uint256) {
return sequencerHistory.length;
}
}
Loading
Loading