diff --git a/Makefile b/Makefile index afde54dbc..3200d9a0d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ################## update dependencies #################### -ETHEREUM_SUBMODULE_COMMIT_OR_TAG := morph-v2.1.2 -ETHEREUM_TARGET_VERSION := morph-v2.1.2 -TENDERMINT_TARGET_VERSION := v0.3.3 +ETHEREUM_SUBMODULE_COMMIT_OR_TAG := test_3_13 +ETHEREUM_TARGET_VERSION := v1.10.14-0.20260227074910-324c53b65341 +TENDERMINT_TARGET_VERSION := v0.3.4-0.20260226093240-9be76fe518c2 ETHEREUM_MODULE_NAME := github.com/morph-l2/go-ethereum diff --git a/bindings/bindings/l1sequencer.go b/bindings/bindings/l1sequencer.go new file mode 100644 index 000000000..80110f035 --- /dev/null +++ b/bindings/bindings/l1sequencer.go @@ -0,0 +1,820 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/morph-l2/go-ethereum" + "github.com/morph-l2/go-ethereum/accounts/abi" + "github.com/morph-l2/go-ethereum/accounts/abi/bind" + "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/core/types" + "github.com/morph-l2/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// L1SequencerMetaData contains all meta data concerning the L1Sequencer contract. +var L1SequencerMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"getSequencer\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sequencer\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateSequencer\",\"inputs\":[{\"name\":\"newSequencer\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SequencerUpdated\",\"inputs\":[{\"name\":\"oldSequencer\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSequencer\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false}]", + Bin: "0x608060405234801561000f575f80fd5b5061081a8061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061007a575f3560e01c8063715018a611610058578063715018a6146100f65780638da5cb5b146100fe578063c4d66de81461011c578063f2fde38b1461012f575f80fd5b806343ae20a31461007e5780634d96a90a146100935780635c1bba38146100d6575b5f80fd5b61009161008c3660046107d3565b610142565b005b60655473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6065546100ad9073ffffffffffffffffffffffffffffffffffffffff1681565b6100916102c7565b60335473ffffffffffffffffffffffffffffffffffffffff166100ad565b61009161012a3660046107d3565b6102da565b61009161013d3660046107d3565b6104ed565b61014a6105a4565b73ffffffffffffffffffffffffffffffffffffffff81166101cc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f696e76616c69642073657175656e63657200000000000000000000000000000060448201526064015b60405180910390fd5b60655473ffffffffffffffffffffffffffffffffffffffff90811690821603610251576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f73616d652073657175656e63657200000000000000000000000000000000000060448201526064016101c3565b6065805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907fcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6905f90a35050565b6102cf6105a4565b6102d85f610625565b565b5f54610100900460ff16158080156102f857505f54600160ff909116105b806103115750303b15801561031157505f5460ff166001145b61039d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016101c3565b5f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156103f9575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b73ffffffffffffffffffffffffffffffffffffffff8216610476576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f696e76616c6964206f776e65720000000000000000000000000000000000000060448201526064016101c3565b61047e61069b565b61048782610625565b80156104e9575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6104f56105a4565b73ffffffffffffffffffffffffffffffffffffffff8116610598576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016101c3565b6105a181610625565b50565b60335473ffffffffffffffffffffffffffffffffffffffff1633146102d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016101c3565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16610731576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016101c3565b6102d85f54610100900460ff166107ca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016101c3565b6102d833610625565b5f602082840312156107e3575f80fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610806575f80fd5b939250505056fea164736f6c6343000818000a", +} + +// L1SequencerABI is the input ABI used to generate the binding from. +// Deprecated: Use L1SequencerMetaData.ABI instead. +var L1SequencerABI = L1SequencerMetaData.ABI + +// L1SequencerBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use L1SequencerMetaData.Bin instead. +var L1SequencerBin = L1SequencerMetaData.Bin + +// DeployL1Sequencer deploys a new Ethereum contract, binding an instance of L1Sequencer to it. +func DeployL1Sequencer(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *L1Sequencer, error) { + parsed, err := L1SequencerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(L1SequencerBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &L1Sequencer{L1SequencerCaller: L1SequencerCaller{contract: contract}, L1SequencerTransactor: L1SequencerTransactor{contract: contract}, L1SequencerFilterer: L1SequencerFilterer{contract: contract}}, nil +} + +// L1Sequencer is an auto generated Go binding around an Ethereum contract. +type L1Sequencer struct { + L1SequencerCaller // Read-only binding to the contract + L1SequencerTransactor // Write-only binding to the contract + L1SequencerFilterer // Log filterer for contract events +} + +// L1SequencerCaller is an auto generated read-only Go binding around an Ethereum contract. +type L1SequencerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1SequencerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type L1SequencerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1SequencerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type L1SequencerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1SequencerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type L1SequencerSession struct { + Contract *L1Sequencer // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// L1SequencerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type L1SequencerCallerSession struct { + Contract *L1SequencerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// L1SequencerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type L1SequencerTransactorSession struct { + Contract *L1SequencerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// L1SequencerRaw is an auto generated low-level Go binding around an Ethereum contract. +type L1SequencerRaw struct { + Contract *L1Sequencer // Generic contract binding to access the raw methods on +} + +// L1SequencerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type L1SequencerCallerRaw struct { + Contract *L1SequencerCaller // Generic read-only contract binding to access the raw methods on +} + +// L1SequencerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type L1SequencerTransactorRaw struct { + Contract *L1SequencerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewL1Sequencer creates a new instance of L1Sequencer, bound to a specific deployed contract. +func NewL1Sequencer(address common.Address, backend bind.ContractBackend) (*L1Sequencer, error) { + contract, err := bindL1Sequencer(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &L1Sequencer{L1SequencerCaller: L1SequencerCaller{contract: contract}, L1SequencerTransactor: L1SequencerTransactor{contract: contract}, L1SequencerFilterer: L1SequencerFilterer{contract: contract}}, nil +} + +// NewL1SequencerCaller creates a new read-only instance of L1Sequencer, bound to a specific deployed contract. +func NewL1SequencerCaller(address common.Address, caller bind.ContractCaller) (*L1SequencerCaller, error) { + contract, err := bindL1Sequencer(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &L1SequencerCaller{contract: contract}, nil +} + +// NewL1SequencerTransactor creates a new write-only instance of L1Sequencer, bound to a specific deployed contract. +func NewL1SequencerTransactor(address common.Address, transactor bind.ContractTransactor) (*L1SequencerTransactor, error) { + contract, err := bindL1Sequencer(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &L1SequencerTransactor{contract: contract}, nil +} + +// NewL1SequencerFilterer creates a new log filterer instance of L1Sequencer, bound to a specific deployed contract. +func NewL1SequencerFilterer(address common.Address, filterer bind.ContractFilterer) (*L1SequencerFilterer, error) { + contract, err := bindL1Sequencer(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &L1SequencerFilterer{contract: contract}, nil +} + +// bindL1Sequencer binds a generic wrapper to an already deployed contract. +func bindL1Sequencer(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := L1SequencerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_L1Sequencer *L1SequencerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L1Sequencer.Contract.L1SequencerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_L1Sequencer *L1SequencerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L1Sequencer.Contract.L1SequencerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L1Sequencer *L1SequencerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L1Sequencer.Contract.L1SequencerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_L1Sequencer *L1SequencerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L1Sequencer.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_L1Sequencer *L1SequencerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L1Sequencer.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L1Sequencer *L1SequencerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L1Sequencer.Contract.contract.Transact(opts, method, params...) +} + +// GetSequencer is a free data retrieval call binding the contract method 0x4d96a90a. +// +// Solidity: function getSequencer() view returns(address) +func (_L1Sequencer *L1SequencerCaller) GetSequencer(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "getSequencer") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetSequencer is a free data retrieval call binding the contract method 0x4d96a90a. +// +// Solidity: function getSequencer() view returns(address) +func (_L1Sequencer *L1SequencerSession) GetSequencer() (common.Address, error) { + return _L1Sequencer.Contract.GetSequencer(&_L1Sequencer.CallOpts) +} + +// GetSequencer is a free data retrieval call binding the contract method 0x4d96a90a. +// +// Solidity: function getSequencer() view returns(address) +func (_L1Sequencer *L1SequencerCallerSession) GetSequencer() (common.Address, error) { + return _L1Sequencer.Contract.GetSequencer(&_L1Sequencer.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L1Sequencer *L1SequencerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L1Sequencer *L1SequencerSession) Owner() (common.Address, error) { + return _L1Sequencer.Contract.Owner(&_L1Sequencer.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_L1Sequencer *L1SequencerCallerSession) Owner() (common.Address, error) { + return _L1Sequencer.Contract.Owner(&_L1Sequencer.CallOpts) +} + +// Sequencer is a free data retrieval call binding the contract method 0x5c1bba38. +// +// Solidity: function sequencer() view returns(address) +func (_L1Sequencer *L1SequencerCaller) Sequencer(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "sequencer") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Sequencer is a free data retrieval call binding the contract method 0x5c1bba38. +// +// Solidity: function sequencer() view returns(address) +func (_L1Sequencer *L1SequencerSession) Sequencer() (common.Address, error) { + return _L1Sequencer.Contract.Sequencer(&_L1Sequencer.CallOpts) +} + +// Sequencer is a free data retrieval call binding the contract method 0x5c1bba38. +// +// Solidity: function sequencer() view returns(address) +func (_L1Sequencer *L1SequencerCallerSession) Sequencer() (common.Address, error) { + return _L1Sequencer.Contract.Sequencer(&_L1Sequencer.CallOpts) +} + +// Initialize is a paid mutator transaction binding the contract method 0xc4d66de8. +// +// Solidity: function initialize(address _owner) returns() +func (_L1Sequencer *L1SequencerTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "initialize", _owner) +} + +// Initialize is a paid mutator transaction binding the contract method 0xc4d66de8. +// +// Solidity: function initialize(address _owner) returns() +func (_L1Sequencer *L1SequencerSession) Initialize(_owner common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.Initialize(&_L1Sequencer.TransactOpts, _owner) +} + +// Initialize is a paid mutator transaction binding the contract method 0xc4d66de8. +// +// Solidity: function initialize(address _owner) returns() +func (_L1Sequencer *L1SequencerTransactorSession) Initialize(_owner common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.Initialize(&_L1Sequencer.TransactOpts, _owner) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L1Sequencer *L1SequencerTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L1Sequencer *L1SequencerSession) RenounceOwnership() (*types.Transaction, error) { + return _L1Sequencer.Contract.RenounceOwnership(&_L1Sequencer.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_L1Sequencer *L1SequencerTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _L1Sequencer.Contract.RenounceOwnership(&_L1Sequencer.TransactOpts) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L1Sequencer *L1SequencerTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L1Sequencer *L1SequencerSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.TransferOwnership(&_L1Sequencer.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_L1Sequencer *L1SequencerTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.TransferOwnership(&_L1Sequencer.TransactOpts, newOwner) +} + +// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// +// Solidity: function updateSequencer(address newSequencer) returns() +func (_L1Sequencer *L1SequencerTransactor) UpdateSequencer(opts *bind.TransactOpts, newSequencer common.Address) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "updateSequencer", newSequencer) +} + +// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// +// Solidity: function updateSequencer(address newSequencer) returns() +func (_L1Sequencer *L1SequencerSession) UpdateSequencer(newSequencer common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer) +} + +// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// +// Solidity: function updateSequencer(address newSequencer) returns() +func (_L1Sequencer *L1SequencerTransactorSession) UpdateSequencer(newSequencer common.Address) (*types.Transaction, error) { + return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer) +} + +// L1SequencerInitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the L1Sequencer contract. +type L1SequencerInitializedIterator struct { + Event *L1SequencerInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L1SequencerInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L1SequencerInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L1SequencerInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L1SequencerInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L1SequencerInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L1SequencerInitialized represents a Initialized event raised by the L1Sequencer contract. +type L1SequencerInitialized struct { + Version uint8 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInitialized is a free log retrieval operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_L1Sequencer *L1SequencerFilterer) FilterInitialized(opts *bind.FilterOpts) (*L1SequencerInitializedIterator, error) { + + logs, sub, err := _L1Sequencer.contract.FilterLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return &L1SequencerInitializedIterator{contract: _L1Sequencer.contract, event: "Initialized", logs: logs, sub: sub}, nil +} + +// WatchInitialized is a free log subscription operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_L1Sequencer *L1SequencerFilterer) WatchInitialized(opts *bind.WatchOpts, sink chan<- *L1SequencerInitialized) (event.Subscription, error) { + + logs, sub, err := _L1Sequencer.contract.WatchLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L1SequencerInitialized) + if err := _L1Sequencer.contract.UnpackLog(event, "Initialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseInitialized is a log parse operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_L1Sequencer *L1SequencerFilterer) ParseInitialized(log types.Log) (*L1SequencerInitialized, error) { + event := new(L1SequencerInitialized) + if err := _L1Sequencer.contract.UnpackLog(event, "Initialized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// L1SequencerOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the L1Sequencer contract. +type L1SequencerOwnershipTransferredIterator struct { + Event *L1SequencerOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L1SequencerOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L1SequencerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L1SequencerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L1SequencerOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L1SequencerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L1SequencerOwnershipTransferred represents a OwnershipTransferred event raised by the L1Sequencer contract. +type L1SequencerOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L1Sequencer *L1SequencerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*L1SequencerOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _L1Sequencer.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &L1SequencerOwnershipTransferredIterator{contract: _L1Sequencer.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L1Sequencer *L1SequencerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *L1SequencerOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _L1Sequencer.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L1SequencerOwnershipTransferred) + if err := _L1Sequencer.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_L1Sequencer *L1SequencerFilterer) ParseOwnershipTransferred(log types.Log) (*L1SequencerOwnershipTransferred, error) { + event := new(L1SequencerOwnershipTransferred) + if err := _L1Sequencer.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// L1SequencerSequencerUpdatedIterator is returned from FilterSequencerUpdated and is used to iterate over the raw logs and unpacked data for SequencerUpdated events raised by the L1Sequencer contract. +type L1SequencerSequencerUpdatedIterator struct { + Event *L1SequencerSequencerUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *L1SequencerSequencerUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(L1SequencerSequencerUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(L1SequencerSequencerUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *L1SequencerSequencerUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L1SequencerSequencerUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L1SequencerSequencerUpdated represents a SequencerUpdated event raised by the L1Sequencer contract. +type L1SequencerSequencerUpdated struct { + OldSequencer common.Address + NewSequencer common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSequencerUpdated is a free log retrieval operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +func (_L1Sequencer *L1SequencerFilterer) FilterSequencerUpdated(opts *bind.FilterOpts, oldSequencer []common.Address, newSequencer []common.Address) (*L1SequencerSequencerUpdatedIterator, error) { + + var oldSequencerRule []interface{} + for _, oldSequencerItem := range oldSequencer { + oldSequencerRule = append(oldSequencerRule, oldSequencerItem) + } + var newSequencerRule []interface{} + for _, newSequencerItem := range newSequencer { + newSequencerRule = append(newSequencerRule, newSequencerItem) + } + + logs, sub, err := _L1Sequencer.contract.FilterLogs(opts, "SequencerUpdated", oldSequencerRule, newSequencerRule) + if err != nil { + return nil, err + } + return &L1SequencerSequencerUpdatedIterator{contract: _L1Sequencer.contract, event: "SequencerUpdated", logs: logs, sub: sub}, nil +} + +// WatchSequencerUpdated is a free log subscription operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +func (_L1Sequencer *L1SequencerFilterer) WatchSequencerUpdated(opts *bind.WatchOpts, sink chan<- *L1SequencerSequencerUpdated, oldSequencer []common.Address, newSequencer []common.Address) (event.Subscription, error) { + + var oldSequencerRule []interface{} + for _, oldSequencerItem := range oldSequencer { + oldSequencerRule = append(oldSequencerRule, oldSequencerItem) + } + var newSequencerRule []interface{} + for _, newSequencerItem := range newSequencer { + newSequencerRule = append(newSequencerRule, newSequencerItem) + } + + logs, sub, err := _L1Sequencer.contract.WatchLogs(opts, "SequencerUpdated", oldSequencerRule, newSequencerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(L1SequencerSequencerUpdated) + if err := _L1Sequencer.contract.UnpackLog(event, "SequencerUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSequencerUpdated is a log parse operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +func (_L1Sequencer *L1SequencerFilterer) ParseSequencerUpdated(log types.Log) (*L1SequencerSequencerUpdated, error) { + event := new(L1SequencerSequencerUpdated) + if err := _L1Sequencer.contract.UnpackLog(event, "SequencerUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/bindings/go.mod b/bindings/go.mod index a509930b7..ecf9560df 100644 --- a/bindings/go.mod +++ b/bindings/go.mod @@ -2,9 +2,9 @@ module morph-l2/bindings go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 -require github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 +require github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect diff --git a/bindings/go.sum b/bindings/go.sum index bb71a7769..84722701a 100644 --- a/bindings/go.sum +++ b/bindings/go.sum @@ -111,8 +111,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.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/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= diff --git a/contracts/contracts/l1/L1Sequencer.sol b/contracts/contracts/l1/L1Sequencer.sol new file mode 100644 index 000000000..3a46768bd --- /dev/null +++ b/contracts/contracts/l1/L1Sequencer.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +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). +contract L1Sequencer is OwnableUpgradeable { + // ============ Storage ============ + + /// @notice Current sequencer address + address public sequencer; + + // ============ Events ============ + + /// @notice Emitted when sequencer is updated + event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer); + + // ============ 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 { + 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"); + + address oldSequencer = sequencer; + sequencer = newSequencer; + + emit SequencerUpdated(oldSequencer, newSequencer); + } + + // ============ View Functions ============ + + /// @notice Get current sequencer address + function getSequencer() external view returns (address) { + return sequencer; + } +} diff --git a/contracts/deploy/013-DeployProxys.ts b/contracts/deploy/013-DeployProxys.ts index 24545613a..6b3a4a5ce 100644 --- a/contracts/deploy/013-DeployProxys.ts +++ b/contracts/deploy/013-DeployProxys.ts @@ -48,6 +48,7 @@ export const deployContractProxies = async ( const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName + const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName @@ -112,6 +113,13 @@ export const deployContractProxies = async ( return err } + // ************************ sequencer contracts deploy ************************ + // L1SequencerProxy deploy + err = await deployContractProxyByStorageName(hre, path, deployer, L1SequencerProxyStorageName) + if (err != "") { + return err + } + // ************************ rollup contracts deploy ************************ // RollupProxy deploy err = await deployContractProxyByStorageName(hre, path, deployer, RollupProxyStorageName) @@ -274,6 +282,7 @@ export const deployContractProxiesConcurrently = async ( ProxyStorageName.L1CrossDomainMessengerProxyStorageName, ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName, ProxyStorageName.L1StakingProxyStorageName, + ProxyStorageName.L1SequencerProxyStorageName, ProxyStorageName.RollupProxyStorageName, ProxyStorageName.L1GatewayRouterProxyStorageName, ProxyStorageName.L1ETHGatewayProxyStorageName, diff --git a/contracts/deploy/014-DeployImpls.ts b/contracts/deploy/014-DeployImpls.ts index ed0653706..b32613018 100644 --- a/contracts/deploy/014-DeployImpls.ts +++ b/contracts/deploy/014-DeployImpls.ts @@ -122,6 +122,11 @@ export const deployContractImplsConcurrently = async ( deployPromises.push(deployContract(L1StakingFactoryName, StakingImplStorageName, [L1CrossDomainMessengerProxyAddress])) + // L1Sequencer deploy (no constructor args) + const L1SequencerFactoryName = ContractFactoryName.L1Sequencer + const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName + deployPromises.push(deployContract(L1SequencerFactoryName, L1SequencerImplStorageName)) + const results = await Promise.all(deployPromises) for (const result of results) { @@ -382,6 +387,21 @@ export const deployContractImpls = async ( return err } + // ************************ sequencer contracts deploy ************************ + // L1Sequencer deploy + const L1SequencerFactoryName = ContractFactoryName.L1Sequencer + const L1SequencerImplStorageName = ImplStorageName.L1SequencerStorageName + Factory = await hre.ethers.getContractFactory(L1SequencerFactoryName) + contract = await Factory.deploy() + await contract.deployed() + console.log("%s=%s ; TX_HASH: %s", L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), contract.deployTransaction.hash) + blockNumber = await hre.ethers.provider.getBlockNumber() + console.log("BLOCK_NUMBER: %s", blockNumber) + err = await storage(path, L1SequencerImplStorageName, contract.address.toLocaleLowerCase(), blockNumber || 0) + if (err != '') { + return err + } + // return return '' } diff --git a/contracts/deploy/019-AdminTransfer.ts b/contracts/deploy/019-AdminTransfer.ts index 566396b90..a0cd1b58a 100644 --- a/contracts/deploy/019-AdminTransfer.ts +++ b/contracts/deploy/019-AdminTransfer.ts @@ -109,6 +109,7 @@ export const AdminTransferConcurrently = async ( ProxyStorageName.L1CrossDomainMessengerProxyStorageName, ProxyStorageName.L1MessageQueueWithGasPriceOracleProxyStorageName, ProxyStorageName.L1StakingProxyStorageName, + ProxyStorageName.L1SequencerProxyStorageName, // Added L1Sequencer ProxyStorageName.RollupProxyStorageName, ProxyStorageName.L1GatewayRouterProxyStorageName, ProxyStorageName.L1ETHGatewayProxyStorageName, @@ -159,6 +160,7 @@ export const AdminTransfer = async ( const RollupProxyStorageName = ProxyStorageName.RollupProxyStorageName const L1StakingProxyStorageName = ProxyStorageName.L1StakingProxyStorageName + const L1SequencerProxyStorageName = ProxyStorageName.L1SequencerProxyStorageName const L1GatewayRouterProxyStorageName = ProxyStorageName.L1GatewayRouterProxyStorageName const L1ETHGatewayProxyStorageName = ProxyStorageName.L1ETHGatewayProxyStorageName @@ -192,6 +194,13 @@ export const AdminTransfer = async ( return err } + // ************************ sequencer contracts admin change ************************ + // L1SequencerProxy admin change + err = await AdminTransferByProxyStorageName(hre, path, deployer, L1SequencerProxyStorageName) + if (err != '') { + return err + } + // ************************ rollup contracts admin change ************************ // RollupProxy admin change err = await AdminTransferByProxyStorageName(hre, path, deployer, RollupProxyStorageName) diff --git a/contracts/deploy/022-SequencerInit.ts b/contracts/deploy/022-SequencerInit.ts new file mode 100644 index 000000000..e8de25116 --- /dev/null +++ b/contracts/deploy/022-SequencerInit.ts @@ -0,0 +1,89 @@ +import "@nomiclabs/hardhat-web3"; +import "@nomiclabs/hardhat-ethers"; +import "@nomiclabs/hardhat-waffle"; + +import { + HardhatRuntimeEnvironment +} from 'hardhat/types'; +import { assertContractVariable, getContractAddressByName, awaitCondition } from "../src/deploy-utils"; +import { ethers } from 'ethers' + +import { + ImplStorageName, + ProxyStorageName, + ContractFactoryName, +} from "../src/types" + +export const SequencerInit = async ( + hre: HardhatRuntimeEnvironment, + path: string, + deployer: any, + configTmp: any +): Promise => { + // L1Sequencer addresses + const L1SequencerProxyAddress = getContractAddressByName(path, ProxyStorageName.L1SequencerProxyStorageName) + const L1SequencerImplAddress = getContractAddressByName(path, ImplStorageName.L1SequencerStorageName) + const L1SequencerFactory = await hre.ethers.getContractFactory(ContractFactoryName.L1Sequencer) + + const IL1SequencerProxy = await hre.ethers.getContractAt(ContractFactoryName.DefaultProxyInterface, L1SequencerProxyAddress, deployer) + + if ( + (await IL1SequencerProxy.implementation()).toLocaleLowerCase() !== L1SequencerImplAddress.toLocaleLowerCase() + ) { + console.log('Upgrading the L1Sequencer proxy...') + + // Owner is the deployer (will be transferred to multisig in production) + const owner = await deployer.getAddress() + + // Get initial sequencer address from config (first sequencer address) + // Note: l2SequencerAddresses is defined in contracts/src/deploy-config/l1.ts + const initialSequencer = (configTmp.l2SequencerAddresses && configTmp.l2SequencerAddresses.length > 0) + ? configTmp.l2SequencerAddresses[0] + : ethers.constants.AddressZero + + console.log('Initial sequencer address:', initialSequencer) + + // Upgrade and initialize the proxy with owner and initial sequencer + // Note: We set sequencer in initialize() to avoid TransparentUpgradeableProxy admin restriction + await IL1SequencerProxy.upgradeToAndCall( + L1SequencerImplAddress, + L1SequencerFactory.interface.encodeFunctionData('initialize', [owner, initialSequencer]) + ) + + await awaitCondition( + async () => { + return ( + (await IL1SequencerProxy.implementation()).toLocaleLowerCase() === L1SequencerImplAddress.toLocaleLowerCase() + ) + }, + 3000, + 1000 + ) + + const contractTmp = new ethers.Contract( + L1SequencerProxyAddress, + L1SequencerFactory.interface, + deployer, + ) + + await assertContractVariable( + contractTmp, + 'owner', + owner, + ) + + if (initialSequencer !== ethers.constants.AddressZero) { + await assertContractVariable( + contractTmp, + 'sequencer', + initialSequencer, + ) + console.log('L1SequencerProxy upgrade success, initial sequencer set:', initialSequencer) + } else { + console.log('L1SequencerProxy upgrade success (no initial sequencer set)') + } + } + return '' +} + +export default SequencerInit diff --git a/contracts/deploy/index.ts b/contracts/deploy/index.ts index 16f69f20b..a30ab90fb 100644 --- a/contracts/deploy/index.ts +++ b/contracts/deploy/index.ts @@ -10,6 +10,7 @@ import StakingInit from './018-StakingInit' import {AdminTransfer,AdminTransferByProxyStorageName, AdminTransferConcurrently} from './019-AdminTransfer' import ContractInit from './020-ContractInit' import StakingRegister from './021-StakingRegister' +import SequencerInit from './022-SequencerInit' export { @@ -28,5 +29,6 @@ export { AdminTransferByProxyStorageName, AdminTransferConcurrently, ContractInit, - StakingRegister + StakingRegister, + SequencerInit } \ No newline at end of file diff --git a/contracts/go.mod b/contracts/go.mod index 514d63ee1..6ee5a498f 100644 --- a/contracts/go.mod +++ b/contracts/go.mod @@ -2,11 +2,11 @@ module morph-l2/contract go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( github.com/iden3/go-iden3-crypto v0.0.16 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/stretchr/testify v1.10.0 ) diff --git a/contracts/go.sum b/contracts/go.sum index d0f44f830..99139efab 100644 --- a/contracts/go.sum +++ b/contracts/go.sum @@ -138,8 +138,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.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/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= diff --git a/contracts/src/types.ts b/contracts/src/types.ts index ea2577c89..7e518addd 100644 --- a/contracts/src/types.ts +++ b/contracts/src/types.ts @@ -18,6 +18,8 @@ const ContractFactoryName = { MultipleVersionRollupVerifier: 'MultipleVersionRollupVerifier', // staking L1Staking: 'L1Staking', + // L1 sequencer + L1Sequencer: 'L1Sequencer', // gateway L1GatewayRouter: 'L1GatewayRouter', L1StandardERC20Gateway: 'L1StandardERC20Gateway', @@ -40,6 +42,8 @@ const ProxyStorageName = { RollupProxyStorageName: 'Proxy__Rollup', // staking L1StakingProxyStorageName: 'Proxy__L1Staking', + // L1 sequencer + L1SequencerProxyStorageName: 'Proxy__L1Sequencer', // gateway L1GatewayRouterProxyStorageName: 'Proxy__L1GatewayRouter', L1StandardERC20GatewayProxyStorageName: 'Proxy__L1StandardERC20Gateway', @@ -71,6 +75,8 @@ const ImplStorageName = { MultipleVersionRollupVerifierStorageName: 'Impl__MultipleVersionRollupVerifier', // staking L1StakingStorageName: 'Impl__L1Staking', + // L1 sequencer + L1SequencerStorageName: 'Impl__L1Sequencer', // gateway L1GatewayRouterStorageName: 'Impl__L1GatewayRouter', L1StandardERC20GatewayStorageName: 'Impl__L1StandardERC20Gateway', diff --git a/contracts/tasks/deploy.ts b/contracts/tasks/deploy.ts index 9ee6739a9..a7ea6bb6a 100644 --- a/contracts/tasks/deploy.ts +++ b/contracts/tasks/deploy.ts @@ -21,6 +21,7 @@ import { AdminTransferConcurrently, ContractInit, StakingRegister, + SequencerInit, } from '../deploy/index' import { ethers } from "ethers"; @@ -120,6 +121,12 @@ task("initialize") console.log('Staking init failed, err: ', err) return } + console.log('\n---------------------------------- Sequencer init ----------------------------------') + err = await SequencerInit(hre, storagePath, deployer, config) + if (err != '') { + console.log('Sequencer init failed, err: ', err) + return + } console.log('\n---------------------------------- Admin Transfer ----------------------------------') if (concurrent === 'true') { err = await AdminTransferConcurrently(hre, storagePath, deployer, config) diff --git a/go.work.sum b/go.work.sum index 8c91e41ad..d1119cff6 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1015,6 +1015,10 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/morph-l2/go-ethereum v1.10.14-0.20251125061742-69718a9dcab9/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= github.com/morph-l2/go-ethereum v1.10.14-0.20260206063816-522b70a5f16f h1:e8gfduHc4AKlR0fD6J3HXveP2Gp4PMvN2UfA9CYEvEc= github.com/morph-l2/go-ethereum v1.10.14-0.20260206063816-522b70a5f16f/go.mod h1:tiFPeidxjoCmLj18ne9H3KQdIGTCvRC30qlef06Fd9M= +github.com/morph-l2/go-ethereum v1.10.14-0.20260227074910-324c53b65341 h1:kupvcg2mxi6WpWPMrGNRGHfpXhkz7IiORwE3kSExwDE= +github.com/morph-l2/go-ethereum v1.10.14-0.20260227074910-324c53b65341/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/tendermint v0.3.3-0.20260226075902-3692a2a2889c h1:CzaQ/rK3nrqylN8JVr2htAsnu2xlg4u99SjzudzxrpM= +github.com/morph-l2/tendermint v0.3.3-0.20260226075902-3692a2a2889c/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= diff --git a/node/blocktag/config.go b/node/blocktag/config.go index 6c392ce0f..43c282800 100644 --- a/node/blocktag/config.go +++ b/node/blocktag/config.go @@ -20,7 +20,6 @@ const ( // Config holds the configuration for BlockTagService type Config struct { - L1Addr string RollupAddress common.Address SafeConfirmations uint64 PollInterval time.Duration @@ -36,8 +35,6 @@ func DefaultConfig() *Config { // SetCliContext sets the configuration from CLI context func (c *Config) SetCliContext(ctx *cli.Context) error { - c.L1Addr = ctx.GlobalString(flags.L1NodeAddr.Name) - // Determine RollupAddress: use explicit flag, or mainnet default, or error if ctx.GlobalBool(flags.MainnetFlag.Name) { c.RollupAddress = node.MainnetRollupContractAddress diff --git a/node/blocktag/service.go b/node/blocktag/service.go index 86f9b8d70..45f7ecda3 100644 --- a/node/blocktag/service.go +++ b/node/blocktag/service.go @@ -63,22 +63,18 @@ type BlockTagService struct { // NewBlockTagService creates a new BlockTagService func NewBlockTagService( ctx context.Context, + l1Client *ethclient.Client, l2Client *types.RetryableClient, config *Config, logger tmlog.Logger, ) (*BlockTagService, error) { - if config.L1Addr == "" { - return nil, fmt.Errorf("L1 RPC address is required") + if l1Client == nil { + return nil, fmt.Errorf("L1 client is required") } if config.RollupAddress == (common.Address{}) { return nil, fmt.Errorf("Rollup contract address is required") } - l1Client, err := ethclient.Dial(config.L1Addr) - if err != nil { - return nil, fmt.Errorf("failed to connect to L1: %w", err) - } - rollup, err := bindings.NewRollup(config.RollupAddress, l1Client) if err != nil { return nil, fmt.Errorf("failed to create rollup binding: %w", err) @@ -122,7 +118,6 @@ func (s *BlockTagService) Stop() { s.logger.Info("Stopping BlockTagService") s.cancel() <-s.stop - s.l1Client.Close() s.logger.Info("BlockTagService stopped") } diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index 2a71f2a28..b056588c3 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -6,11 +6,17 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" + "time" + "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/crypto" "github.com/morph-l2/go-ethereum/ethclient" + tmlog "github.com/tendermint/tendermint/libs/log" tmnode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/upgrade" "github.com/urfave/cli" "morph-l2/bindings/bindings" @@ -20,6 +26,7 @@ import ( "morph-l2/node/db" "morph-l2/node/derivation" "morph-l2/node/flags" + "morph-l2/node/l1sequencer" "morph-l2/node/sequencer" "morph-l2/node/sequencer/mock" "morph-l2/node/sync" @@ -60,12 +67,20 @@ func L2NodeMain(ctx *cli.Context) error { tmNode *tmnode.Node dvNode *derivation.Derivation blockTagSvc *blocktag.BlockTagService + tracker *l1sequencer.L1Tracker + verifier *l1sequencer.SequencerVerifier + signer l1sequencer.Signer nodeConfig = node.DefaultConfig() ) isMockSequencer := ctx.GlobalBool(flags.MockEnabled.Name) isValidator := ctx.GlobalBool(flags.ValidatorEnable.Name) + // Apply consensus switch height if explicitly set via flag + if ctx.GlobalIsSet(flags.ConsensusSwitchHeight.Name) { + upgrade.SetUpgradeBlockHeight(ctx.GlobalInt64(flags.ConsensusSwitchHeight.Name)) + } + if err = nodeConfig.SetCliContext(ctx); err != nil { return err } @@ -118,14 +133,27 @@ func L2NodeMain(ctx *cli.Context) error { dvNode.Start() nodeConfig.Logger.Info("derivation node starting") } else { - // launch tendermint node + // ========== Create Syncer and L1 Sequencer Components ========== + syncer, err = node.NewSyncer(ctx, home, nodeConfig) + if err != nil { + return fmt.Errorf("failed to create syncer: %w", err) + } + + tracker, verifier, signer, err = initL1SequencerComponents(ctx, syncer.L1Client(), nodeConfig.Logger) + if err != nil { + return fmt.Errorf("failed to init L1 sequencer components: %w", err) + } + + // ========== Launch Tendermint Node ========== tmCfg, err := sequencer.LoadTmConfig(ctx, home) if err != nil { return err } tmVal := privval.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()) pubKey, _ := tmVal.GetPubKey() - newSyncerFunc := func() (*sync.Syncer, error) { return node.NewSyncer(ctx, home, nodeConfig) } + + // Create executor with syncer + newSyncerFunc := func() (*sync.Syncer, error) { return syncer, nil } // Reuse existing syncer executor, err = node.NewExecutor(newSyncerFunc, nodeConfig, pubKey) if err != nil { return err @@ -137,25 +165,19 @@ func L2NodeMain(ctx *cli.Context) error { } go ms.Start() } else { - if tmNode, err = sequencer.SetupNode(tmCfg, tmVal, executor, nodeConfig.Logger); err != nil { - return fmt.Errorf("failed to setup consensus node, error: %v", err) + tmNode, err = sequencer.SetupNode(tmCfg, tmVal, executor, nodeConfig.Logger, verifier, signer) + if err != nil { + return fmt.Errorf("failed to setup consensus node: %v", err) } if err = tmNode.Start(); err != nil { return fmt.Errorf("failed to start consensus node, error: %v", err) } } - // Start BlockTagService for sequencer mode - blockTagConfig := blocktag.DefaultConfig() - if err := blockTagConfig.SetCliContext(ctx); err != nil { - return fmt.Errorf("blocktag config set cli context error: %w", err) - } - blockTagSvc, err = blocktag.NewBlockTagService(context.Background(), executor.L2Client(), blockTagConfig, nodeConfig.Logger) + // ========== Initialize BlockTagService ========== + blockTagSvc, err = initBlockTagService(ctx, syncer.L1Client(), executor, nodeConfig.Logger) if err != nil { - return fmt.Errorf("failed to create BlockTagService: %w", err) - } - if err := blockTagSvc.Start(); err != nil { - return fmt.Errorf("failed to start BlockTagService: %w", err) + return fmt.Errorf("failed to init BlockTagService: %w", err) } } @@ -186,10 +208,102 @@ func L2NodeMain(ctx *cli.Context) error { if blockTagSvc != nil { blockTagSvc.Stop() } + if tracker != nil { + tracker.Stop() + } return nil } +// initL1SequencerComponents initializes all L1 sequencer related components: +// - L1Tracker: monitors L1 sync status +// - SequencerCache: caches L1 sequencer address (nil if contract not configured) +// - Signer: signs blocks (nil if private key not configured) +func initL1SequencerComponents( + ctx *cli.Context, + l1Client *ethclient.Client, + logger tmlog.Logger, +) (*l1sequencer.L1Tracker, *l1sequencer.SequencerVerifier, l1sequencer.Signer, error) { + if l1Client == nil { + return nil, nil, nil, fmt.Errorf("L1 client is required, check l1.rpc configuration") + } + + // Get config from flags + lagThreshold := ctx.GlobalDuration(flags.L1SyncLagThreshold.Name) + if lagThreshold == 0 { + lagThreshold = 5 * time.Minute // default + } + contractAddr := common.HexToAddress(ctx.GlobalString(flags.L1SequencerContractAddr.Name)) + seqPrivKeyHex := ctx.GlobalString(flags.SequencerPrivateKey.Name) + + // Initialize L1 Tracker + tracker := l1sequencer.NewL1Tracker(context.Background(), l1Client, lagThreshold, logger) + if err := tracker.Start(); err != nil { + return nil, nil, nil, fmt.Errorf("failed to start L1 tracker: %w", err) + } + logger.Info("L1 Tracker started", "lagThreshold", lagThreshold) + + // Initialize Sequencer Verifier (optional) + var verifier *l1sequencer.SequencerVerifier + if contractAddr != (common.Address{}) { + caller, err := bindings.NewL1SequencerCaller(contractAddr, l1Client) + if err != nil { + tracker.Stop() + return nil, nil, nil, fmt.Errorf("failed to create L1Sequencer caller: %w", err) + } + verifier = l1sequencer.NewSequencerVerifier(caller, logger) + logger.Info("Sequencer verifier initialized", "contract", contractAddr.Hex()) + } else { + logger.Info("L1 Sequencer contract not configured, verifier disabled") + } + + // Initialize Signer (optional) + var signer l1sequencer.Signer + if seqPrivKeyHex != "" { + seqPrivKeyHex = strings.TrimPrefix(seqPrivKeyHex, "0x") + privKey, err := crypto.HexToECDSA(seqPrivKeyHex) + if err != nil { + tracker.Stop() + return nil, nil, nil, fmt.Errorf("invalid sequencer private key: %w", err) + } + signer, err = l1sequencer.NewLocalSigner(privKey, verifier, logger) + if err != nil { + tracker.Stop() + return nil, nil, nil, err + } + logger.Info("Sequencer signer initialized", "address", signer.Address().Hex()) + } else { + logger.Info("Sequencer private key not configured, signer disabled") + } + + return tracker, verifier, signer, nil +} + +// initBlockTagService initializes the block tag service +func initBlockTagService( + ctx *cli.Context, + l1Client *ethclient.Client, + executor *node.Executor, + logger tmlog.Logger, +) (*blocktag.BlockTagService, error) { + config := blocktag.DefaultConfig() + if err := config.SetCliContext(ctx); err != nil { + return nil, err + } + + svc, err := blocktag.NewBlockTagService(context.Background(), l1Client, executor.L2Client(), config, logger) + if err != nil { + return nil, err + } + + if err := svc.Start(); err != nil { + return nil, err + } + + logger.Info("BlockTagService started") + return svc, nil +} + func homeDir(ctx *cli.Context) (string, error) { home := ctx.GlobalString(flags.Home.Name) if home == "" { diff --git a/node/core/batch.go b/node/core/batch.go index 9c851956e..167c3c2e5 100644 --- a/node/core/batch.go +++ b/node/core/batch.go @@ -178,14 +178,14 @@ func (e *Executor) CalculateCapWithProposalBlock(currentBlockBytes []byte, curre return false, err } - // MPT fork: force batch points on the 1st and 2nd post-fork blocks, so the 1st post-fork block + // Jade fork: force batch points on the 1st and 2nd post-fork blocks, so the 1st post-fork block // becomes a single-block batch: [H1, H2). - force, err := e.forceBatchPointForMPTFork(height, block.Timestamp, block.StateRoot, block.Hash) + force, err := e.forceBatchPointForJadeFork(height, block.Timestamp, block.StateRoot, block.Hash) if err != nil { return false, err } if force { - e.logger.Info("MPT fork: force batch point", "height", height, "timestamp", block.Timestamp) + e.logger.Info("Jade fork: force batch point", "height", height, "timestamp", block.Timestamp) return true, nil } @@ -198,27 +198,27 @@ func (e *Executor) CalculateCapWithProposalBlock(currentBlockBytes []byte, curre return exceeded, err } -// forceBatchPointForMPTFork forces batch points at the 1st and 2nd block after the MPT fork time. +// forceBatchPointForJadeFork forces batch points at the 1st and 2nd block after the Jade fork time. // // Design goals: // - Minimal change: only affects batch-point decision logic. // - Stability: CalculateCapWithProposalBlock can be called multiple times at the same height; return must be consistent. // - Performance: after handling (or skipping beyond) the fork boundary, no more HeaderByNumber calls are made. -func (e *Executor) forceBatchPointForMPTFork(height uint64, blockTime uint64, stateRoot common.Hash, blockHash common.Hash) (bool, error) { +func (e *Executor) forceBatchPointForJadeFork(height uint64, blockTime uint64, stateRoot common.Hash, blockHash common.Hash) (bool, error) { // If we already decided to force at this height, keep returning true without extra RPCs. - if e.mptForkForceHeight == height && height != 0 { + if e.jadeForkForceHeight == height && height != 0 { return true, nil } // If fork boundary is already handled and this isn't a forced height, fast exit. - if e.mptForkStage >= 2 { + if e.jadeForkStage >= 2 { return false, nil } // Ensure we have fork time cached (0 means disabled). - if e.mptForkTime == 0 { - e.mptForkTime = e.l2Client.MPTForkTime() + if e.jadeForkTime == 0 { + e.jadeForkTime = e.l2Client.JadeForkTime() } - forkTime := e.mptForkTime + forkTime := e.jadeForkTime if forkTime == 0 || blockTime < forkTime { return false, nil } @@ -234,24 +234,24 @@ func (e *Executor) forceBatchPointForMPTFork(height uint64, blockTime uint64, st if parent.Time < forkTime { // Log H1 (the 1st post-fork block) state root // This stateRoot is intended to be used as the Rollup contract "genesis state root" - // when we reset/re-initialize the genesis state root during the MPT upgrade. + // when we reset/re-initialize the genesis state root during the Jade upgrade. e.logger.Info( - "MPT_FORK_H1_GENESIS_STATE_ROOT", + "JADE_FORK_H1_GENESIS_STATE_ROOT", "height", height, "timestamp", blockTime, "forkTime", forkTime, "stateRoot", stateRoot.Hex(), "blockHash", blockHash.Hex(), ) - e.mptForkStage = 1 - e.mptForkForceHeight = height + e.jadeForkStage = 1 + e.jadeForkForceHeight = height return true, nil } // If parent is already post-fork, we may be at the 2nd post-fork block (H2) or later. if height < 2 { // We cannot be H2; mark done to avoid future calls. - e.mptForkStage = 2 + e.jadeForkStage = 2 return false, nil } @@ -261,13 +261,13 @@ func (e *Executor) forceBatchPointForMPTFork(height uint64, blockTime uint64, st } if grandParent.Time < forkTime { // This is H2 (2nd post-fork block). - e.mptForkStage = 2 - e.mptForkForceHeight = height + e.jadeForkStage = 2 + e.jadeForkForceHeight = height return true, nil } // Beyond H2: nothing to do (can't retroactively fix). Mark done for performance. - e.mptForkStage = 2 + e.jadeForkStage = 2 return false, nil } diff --git a/node/core/executor.go b/node/core/executor.go index 52d30125d..08f20bf17 100644 --- a/node/core/executor.go +++ b/node/core/executor.go @@ -56,12 +56,12 @@ type Executor struct { rollupABI *abi.ABI batchingCache *BatchingCache - // MPT fork handling: force batch points at the 1st and 2nd block after fork. + // Jade fork handling: force batch points at the 1st and 2nd block after fork. // This state machine exists to avoid repeated HeaderByNumber calls after the fork is handled, // while keeping results stable if CalculateCapWithProposalBlock is called multiple times at the same height. - mptForkTime uint64 // cached from geth eth_config.morph.mptForkTime (0 means disabled/unknown) - mptForkStage uint8 // 0: not handled, 1: forced H1, 2: done (forced H2 or skipped beyond H2) - mptForkForceHeight uint64 // if equals current height, must return true (stability across multiple calls) + jadeForkTime uint64 // cached from geth eth_config.morph.jadeForkTime (0 means disabled/unknown) + jadeForkStage uint8 // 0: not handled, 1: forced H1, 2: done (forced H2 or skipped beyond H2) + jadeForkForceHeight uint64 // if equals current height, must return true (stability across multiple calls) logger tmlog.Logger metrics *Metrics @@ -155,7 +155,7 @@ func NewExecutor(newSyncFunc NewSyncerFunc, config *Config, tmPubKey crypto.PubK batchingCache: NewBatchingCache(), UpgradeBatchTime: config.UpgradeBatchTime, blsKeyCheckForkHeight: config.BlsKeyCheckForkHeight, - mptForkTime: l2Client.MPTForkTime(), + jadeForkTime: l2Client.JadeForkTime(), logger: logger, metrics: PrometheusMetrics("morphnode"), } @@ -482,3 +482,202 @@ func (e *Executor) getParamsAndValsAtHeight(height int64) (*tmproto.BatchParams, func (e *Executor) L2Client() *types.RetryableClient { return e.l2Client } + +// ============================================================================ +// L2NodeV2 interface implementation for sequencer mode +// ============================================================================ + +// RequestBlockDataV2 requests block data based on parent hash. +// This differs from RequestBlockData which uses height. +// Using parent hash allows for proper fork chain handling in the future. +func (e *Executor) RequestBlockDataV2(parentHashBytes []byte) (*l2node.BlockV2, bool, error) { + if e.l1MsgReader == nil { + return nil, false, fmt.Errorf("RequestBlockDataV2 is not allowed to be called") + } + parentHash := common.BytesToHash(parentHashBytes) + + // Read L1 messages + fromIndex := e.nextL1MsgIndex + l1Messages := e.l1MsgReader.ReadL1MessagesInRange(fromIndex, fromIndex+e.maxL1MsgNumPerBlock-1) + transactions := make(eth.Transactions, len(l1Messages)) + + collectedL1Msgs := false + if len(l1Messages) > 0 { + queueIndex := fromIndex + for i, l1Message := range l1Messages { + transaction := eth.NewTx(&l1Message.L1MessageTx) + transactions[i] = transaction + if queueIndex != l1Message.QueueIndex { + e.logger.Error("unexpected l1message queue index", "expected", queueIndex, "actual", l1Message.QueueIndex) + return nil, false, types.ErrInvalidL1MessageOrder + } + queueIndex++ + } + collectedL1Msgs = true + } + + // Call geth to assemble block based on parent hash + l2Block, err := e.l2Client.AssembleL2BlockV2(context.Background(), parentHash, transactions) + if err != nil { + e.logger.Error("failed to assemble block v2", "parentHash", parentHash.Hex(), "error", err) + return nil, false, err + } + + e.logger.Info("AssembleL2BlockV2 success ", + "number", l2Block.Number, + "hash", l2Block.Hash.Hex(), + "parentHash", l2Block.ParentHash.Hex(), + "tx length", len(l2Block.Transactions), + "collectedL1Msgs", collectedL1Msgs, + ) + + return executableL2DataToBlockV2(l2Block), collectedL1Msgs, nil +} + +// ApplyBlockV2 applies a block to the L2 execution layer. +// This is used in sequencer mode after block validation. +func (e *Executor) ApplyBlockV2(block *l2node.BlockV2) error { + // Convert BlockV2 to ExecutableL2Data for geth + execBlock := blockV2ToExecutableL2Data(block) + + // Check if block is already applied + height, err := e.l2Client.BlockNumber(context.Background()) + if err != nil { + return err + } + + if execBlock.Number <= height { + e.logger.Info("ignore it, the block was already applied", "block number", execBlock.Number) + return nil + } + + // We only accept continuous blocks + if execBlock.Number > height+1 { + return types.ErrWrongBlockNumber + } + + // Apply the block (no batch hash in sequencer mode for now) + err = e.l2Client.NewL2Block(context.Background(), execBlock, nil) + if err != nil { + e.logger.Error("failed to apply block v2", "error", err) + return err + } + + // Update L1 message index + e.updateNextL1MessageIndex(execBlock) + + e.metrics.Height.Set(float64(execBlock.Number)) + e.logger.Info("ApplyBlockV2 success", "number", execBlock.Number, "hash", execBlock.Hash.Hex()) + + return nil +} + +// GetBlockByNumber retrieves a block by its number from the L2 execution layer. +// Uses standard eth_getBlockByNumber JSON-RPC. +func (e *Executor) GetBlockByNumber(height uint64) (*l2node.BlockV2, error) { + block, err := e.l2Client.BlockByNumber(context.Background(), big.NewInt(int64(height))) + if err != nil { + e.logger.Error("failed to get block by number", "height", height, "error", err) + return nil, err + } + return ethBlockToBlockV2(block) +} + +// GetLatestBlockV2 returns the latest block from the L2 execution layer. +// Uses standard eth_getBlockByNumber JSON-RPC with nil (latest). +func (e *Executor) GetLatestBlockV2() (*l2node.BlockV2, error) { + block, err := e.l2Client.BlockByNumber(context.Background(), nil) + if err != nil { + e.logger.Error("failed to get latest block", "error", err) + return nil, err + } + return ethBlockToBlockV2(block) +} + +// ==================== Type Conversion Functions ==================== + +// ethBlockToBlockV2 converts eth.Block to BlockV2 +func ethBlockToBlockV2(block *eth.Block) (*l2node.BlockV2, error) { + if block == nil { + return nil, fmt.Errorf("block is nil") + } + header := block.Header() + + // Encode transactions using MarshalBinary (handles EIP-2718 typed transactions correctly) + // Initialize as empty slice (not nil) to ensure JSON serialization produces [] instead of null + txs := make([][]byte, 0, len(block.Transactions())) + for _, tx := range block.Transactions() { + bz, err := tx.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("failed to marshal tx, error: %v", err) + } + txs = append(txs, bz) + } + + return &l2node.BlockV2{ + ParentHash: header.ParentHash, + Miner: header.Coinbase, + Number: header.Number.Uint64(), + GasLimit: header.GasLimit, + BaseFee: header.BaseFee, + Timestamp: header.Time, + Transactions: txs, + StateRoot: header.Root, + GasUsed: header.GasUsed, + ReceiptRoot: header.ReceiptHash, + LogsBloom: header.Bloom.Bytes(), + NextL1MessageIndex: header.NextL1MsgIndex, + Hash: block.Hash(), + }, nil +} + +// blockV2ToExecutableL2Data converts BlockV2 to ExecutableL2Data +func blockV2ToExecutableL2Data(block *l2node.BlockV2) *catalyst.ExecutableL2Data { + if block == nil { + return nil + } + // Ensure Transactions is not nil (JSON requires [] not null) + txs := block.Transactions + if txs == nil { + txs = make([][]byte, 0) + } + return &catalyst.ExecutableL2Data{ + ParentHash: block.ParentHash, + Miner: block.Miner, + Number: block.Number, + GasLimit: block.GasLimit, + BaseFee: block.BaseFee, + Timestamp: block.Timestamp, + Transactions: txs, + StateRoot: block.StateRoot, + GasUsed: block.GasUsed, + ReceiptRoot: block.ReceiptRoot, + LogsBloom: block.LogsBloom, + WithdrawTrieRoot: block.WithdrawTrieRoot, + NextL1MessageIndex: block.NextL1MessageIndex, + Hash: block.Hash, + } +} + +// executableL2DataToBlockV2 converts ExecutableL2Data to BlockV2 +func executableL2DataToBlockV2(data *catalyst.ExecutableL2Data) *l2node.BlockV2 { + if data == nil { + return nil + } + return &l2node.BlockV2{ + ParentHash: data.ParentHash, + Miner: data.Miner, + Number: data.Number, + GasLimit: data.GasLimit, + BaseFee: data.BaseFee, + Timestamp: data.Timestamp, + Transactions: data.Transactions, + StateRoot: data.StateRoot, + GasUsed: data.GasUsed, + ReceiptRoot: data.ReceiptRoot, + LogsBloom: data.LogsBloom, + WithdrawTrieRoot: data.WithdrawTrieRoot, + NextL1MessageIndex: data.NextL1MessageIndex, + Hash: data.Hash, + } +} diff --git a/node/flags/flags.go b/node/flags/flags.go index 2c00f4a87..3adc4adb9 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -1,6 +1,9 @@ package flags -import "github.com/urfave/cli" +import ( + "github.com/urfave/cli" + "time" +) const envVarPrefix = "MORPH_NODE_" @@ -240,6 +243,27 @@ var ( Value: 10, } + // L1 Sequencer options + L1SequencerContractAddr = cli.StringFlag{ + Name: "l1.sequencerContract", + Usage: "L1 Sequencer contract address for signature verification", + EnvVar: prefixEnvVar("L1_SEQUENCER_CONTRACT"), + } + + L1SyncLagThreshold = cli.DurationFlag{ + Name: "l1.syncLagThreshold", + Usage: "L1 sync lag threshold for warning logs", + EnvVar: prefixEnvVar("L1_SYNC_LAG_THRESHOLD"), + Value: 5 * time.Minute, + } + + // Sequencer private key for block signing (hex encoded, without 0x prefix) + SequencerPrivateKey = cli.StringFlag{ + Name: "sequencer.privateKey", + Usage: "Sequencer private key for block signing (hex encoded)", + EnvVar: prefixEnvVar("SEQUENCER_PRIVATE_KEY"), + } + // Batch rules UpgradeBatchTime = cli.Uint64Flag{ Name: "upgrade.batchTime", @@ -251,6 +275,14 @@ var ( Usage: "Morph mainnet", } + // for test + ConsensusSwitchHeight = cli.Int64Flag{ + Name: "consensus.switchHeight", + Usage: "Block height at which the consensus switches to sequencer mode. Default -1 means upgrade disabled.", + EnvVar: prefixEnvVar("CONSENSUS_SWITCH_HEIGHT"), + Value: -1, + } + DerivationConfirmations = cli.Int64Flag{ Name: "derivation.confirmations", Usage: "The number of confirmations needed on L1 for finalization. If not set, the default value is l1.confirmations", @@ -369,6 +401,14 @@ var Flags = []cli.Flag{ // blocktag options BlockTagSafeConfirmations, + // L1 Sequencer options + L1SequencerContractAddr, + L1SyncLagThreshold, + SequencerPrivateKey, + + // consensus + ConsensusSwitchHeight, + // batch rules UpgradeBatchTime, MainnetFlag, diff --git a/node/go.mod b/node/go.mod index d8c7bff2e..026656f3a 100644 --- a/node/go.mod +++ b/node/go.mod @@ -2,7 +2,7 @@ module morph-l2/node go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( github.com/cenkalti/backoff/v4 v4.1.3 @@ -11,7 +11,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/holiman/uint256 v1.2.4 github.com/klauspost/compress v1.17.9 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/prometheus/client_golang v1.17.0 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.10.0 diff --git a/node/go.sum b/node/go.sum index dda7b959d..fb65d59d4 100644 --- a/node/go.sum +++ b/node/go.sum @@ -361,10 +361,10 @@ 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.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= -github.com/morph-l2/tendermint v0.3.3 h1:zsmzVJfKp+NuCr45ZUUY2ZJjnHAVLzwJLID6GxBR4i4= -github.com/morph-l2/tendermint v0.3.3/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 h1:bnPBsPMWnN0bHhWvMMFp6dp0RB9Z9P1NuKGvFwu8V7E= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= 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= diff --git a/node/l1sequencer/signer.go b/node/l1sequencer/signer.go new file mode 100644 index 000000000..f03901ae3 --- /dev/null +++ b/node/l1sequencer/signer.go @@ -0,0 +1,71 @@ +package l1sequencer + +import ( + "context" + "crypto/ecdsa" + "fmt" + + "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/crypto" + tmlog "github.com/tendermint/tendermint/libs/log" +) + +// Signer manages sequencer identity and signing capabilities. +// It abstracts the private key management, allowing for local key storage +// or remote signing services (e.g., HSM, KMS) in the future. +type Signer interface { + // Sign signs data with the sequencer's private key + Sign(data []byte) ([]byte, error) + + // Address returns the sequencer's address + Address() common.Address + + // IsActiveSequencer checks if this signer is the current L1 sequencer + IsActiveSequencer(ctx context.Context) (bool, error) +} + +// LocalSigner implements Signer with a local private key +type LocalSigner struct { + privKey *ecdsa.PrivateKey + address common.Address + verifier *SequencerVerifier + logger tmlog.Logger +} + +// NewLocalSigner creates a new LocalSigner with a local private key +func NewLocalSigner(privKey *ecdsa.PrivateKey, verifier *SequencerVerifier, logger tmlog.Logger) (*LocalSigner, error) { + if privKey == nil { + return nil, fmt.Errorf("private key is required") + } + + address := crypto.PubkeyToAddress(privKey.PublicKey) + + return &LocalSigner{ + privKey: privKey, + address: address, + verifier: verifier, + logger: logger.With("module", "signer"), + }, nil +} + +// Sign signs data with the sequencer's private key +func (s *LocalSigner) Sign(data []byte) ([]byte, error) { + signature, err := crypto.Sign(data, s.privKey) + if err != nil { + return nil, fmt.Errorf("failed to sign: %w", err) + } + return signature, nil +} + +// Address returns the sequencer's address +func (s *LocalSigner) Address() common.Address { + return s.address +} + +// IsActiveSequencer checks if this signer is the current L1 sequencer +func (s *LocalSigner) IsActiveSequencer(ctx context.Context) (bool, error) { + if s.verifier == nil { + return false, fmt.Errorf("sequencer verifier not set") + } + return s.verifier.IsSequencer(ctx, s.address) +} diff --git a/node/l1sequencer/tracker.go b/node/l1sequencer/tracker.go new file mode 100644 index 000000000..d8ea3b8c0 --- /dev/null +++ b/node/l1sequencer/tracker.go @@ -0,0 +1,86 @@ +package l1sequencer + +import ( + "context" + "time" + + "github.com/morph-l2/go-ethereum/ethclient" + tmlog "github.com/tendermint/tendermint/libs/log" +) + +// L1Tracker monitors L1 RPC sync status and logs warnings if behind. +// It runs as an independent service. +type L1Tracker struct { + ctx context.Context + cancel context.CancelFunc + l1Client *ethclient.Client + lagThreshold time.Duration + logger tmlog.Logger + stop chan struct{} +} + +// NewL1Tracker creates a new L1Tracker +func NewL1Tracker( + ctx context.Context, + l1Client *ethclient.Client, + lagThreshold time.Duration, + logger tmlog.Logger, +) *L1Tracker { + ctx, cancel := context.WithCancel(ctx) + return &L1Tracker{ + ctx: ctx, + cancel: cancel, + l1Client: l1Client, + lagThreshold: lagThreshold, + logger: logger.With("module", "l1tracker"), + stop: make(chan struct{}), + } +} + +// Start starts the L1Tracker +func (t *L1Tracker) Start() error { + t.logger.Info("Starting L1Tracker", "lagThreshold", t.lagThreshold) + go t.loop() + return nil +} + +// Stop stops the L1Tracker +func (t *L1Tracker) Stop() { + t.logger.Info("Stopping L1Tracker") + t.cancel() + <-t.stop +} + +func (t *L1Tracker) loop() { + defer close(t.stop) + + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-t.ctx.Done(): + return + case <-ticker.C: + t.checkL1SyncLag() + } + } +} + +func (t *L1Tracker) checkL1SyncLag() { + header, err := t.l1Client.HeaderByNumber(t.ctx, nil) + if err != nil { + t.logger.Error("Failed to get L1 header", "error", err) + return + } + + blockTime := time.Unix(int64(header.Time), 0) + lag := time.Since(blockTime) + if lag > t.lagThreshold { + t.logger.Error("L1 RPC is behind", + "latestBlock", header.Number.Uint64(), + "blockTime", blockTime.Format(time.RFC3339), + "lag", lag.Round(time.Second), + ) + } +} diff --git a/node/l1sequencer/verifier.go b/node/l1sequencer/verifier.go new file mode 100644 index 000000000..1cbf8517a --- /dev/null +++ b/node/l1sequencer/verifier.go @@ -0,0 +1,97 @@ +package l1sequencer + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/morph-l2/go-ethereum/accounts/abi/bind" + "github.com/morph-l2/go-ethereum/common" + tmlog "github.com/tendermint/tendermint/libs/log" + + "morph-l2/bindings/bindings" +) + +const ( + // CacheTTL is the time-to-live for the sequencer verifier cache + //CacheTTL = 30 * time.Minute + CacheTTL = 10 * time.Second +) + +// SequencerVerifier verifies L1 sequencer status with caching. +// It provides IsSequencer() for checking if an address is the current sequencer. +type SequencerVerifier struct { + mutex sync.Mutex + sequencer common.Address + cacheExpiry time.Time + + caller *bindings.L1SequencerCaller + logger tmlog.Logger +} + +// NewSequencerVerifier creates a new SequencerVerifier +func NewSequencerVerifier(caller *bindings.L1SequencerCaller, logger tmlog.Logger) *SequencerVerifier { + return &SequencerVerifier{ + caller: caller, + logger: logger.With("module", "l1sequencer_verifier"), + } +} + +// flushCache refreshes the cache (caller must hold the lock) +func (c *SequencerVerifier) flushCache(ctx context.Context) error { + newSeq, err := c.caller.GetSequencer(&bind.CallOpts{Context: ctx}) + if err != nil { + return fmt.Errorf("failed to get sequencer from L1: %w", err) + } + + if c.sequencer != newSeq { + c.logger.Info("Sequencer address updated", + "old", c.sequencer.Hex(), + "new", newSeq.Hex()) + } + + c.sequencer = newSeq + c.cacheExpiry = time.Now().Add(CacheTTL) + return nil +} + +// IsSequencer checks if the given address is the current sequencer. +// It uses lazy loading: refreshes cache if expired, and retries on miss. +func (c *SequencerVerifier) IsSequencer(ctx context.Context, addr common.Address) (bool, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + // Cache expired, refresh + if time.Now().After(c.cacheExpiry) { + if err := c.flushCache(ctx); err != nil { + return false, err + } + } + + // Cache hit + if c.sequencer == addr { + return true, nil + } + + // Cache miss - maybe sequencer just updated, force refresh once + if err := c.flushCache(ctx); err != nil { + return false, err + } + + return c.sequencer == addr, nil +} + +// GetSequencer returns the cached sequencer address (refreshes if expired) +func (c *SequencerVerifier) GetSequencer(ctx context.Context) (common.Address, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + if time.Now().After(c.cacheExpiry) { + if err := c.flushCache(ctx); err != nil { + return common.Address{}, err + } + } + + return c.sequencer, nil +} diff --git a/node/sequencer/tm_node.go b/node/sequencer/tm_node.go index 6f6bf994c..b9cbffe38 100644 --- a/node/sequencer/tm_node.go +++ b/node/sequencer/tm_node.go @@ -14,11 +14,13 @@ import ( tmnode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" + tmsequencer "github.com/tendermint/tendermint/sequencer" "github.com/tendermint/tendermint/types" "github.com/urfave/cli" node "morph-l2/node/core" "morph-l2/node/flags" + "morph-l2/node/l1sequencer" nodetypes "morph-l2/node/types" ) @@ -51,7 +53,17 @@ func LoadTmConfig(ctx *cli.Context, home string) (*config.Config, error) { return tmCfg, nil } -func SetupNode(tmCfg *config.Config, privValidator types.PrivValidator, executor *node.Executor, logger tmlog.Logger) (*tmnode.Node, error) { +// SetupNode creates a tendermint node with the given configuration. +// verifier: L1 sequencer verifier for signature verification (optional, can be nil) +// signer: sequencer signer for block signing (optional, can be nil) +func SetupNode( + tmCfg *config.Config, + privValidator types.PrivValidator, + executor *node.Executor, + logger tmlog.Logger, + verifier *l1sequencer.SequencerVerifier, + signer l1sequencer.Signer, +) (*tmnode.Node, error) { nodeLogger := logger.With("module", "main") nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) @@ -67,7 +79,12 @@ func SetupNode(tmCfg *config.Config, privValidator types.PrivValidator, executor return nil, fmt.Errorf("failed to load bls priv key") } - //var app types.Application + // Build verifier (SequencerVerifier implements tmsequencer.SequencerVerifier interface) + var tmVerifier tmsequencer.SequencerVerifier + if verifier != nil { + tmVerifier = verifier + } + n, err := tmnode.NewNode( tmCfg, executor, @@ -79,6 +96,8 @@ func SetupNode(tmCfg *config.Config, privValidator types.PrivValidator, executor tmnode.DefaultDBProvider, tmnode.DefaultMetricsProvider(tmCfg.Instrumentation), nodeLogger, + tmVerifier, + signer, ) return n, err } diff --git a/node/sync/bridge_client.go b/node/sync/bridge_client.go index bab51c45e..e8a73abf3 100644 --- a/node/sync/bridge_client.go +++ b/node/sync/bridge_client.go @@ -24,6 +24,11 @@ type BridgeClient struct { logger tmlog.Logger } +// L1Client returns the underlying L1 client (for sharing with other services) +func (c *BridgeClient) L1Client() *ethclient.Client { + return c.l1Client +} + func NewBridgeClient(l1Client *ethclient.Client, l1MessageQueueAddress common.Address, confirmations rpc.BlockNumber, logger tmlog.Logger) (*BridgeClient, error) { logger = logger.With("module", "bridge") filter, err := bindings.NewL1MessageQueueWithGasPriceOracleFilterer(l1MessageQueueAddress, l1Client) diff --git a/node/sync/syncer.go b/node/sync/syncer.go index 9c109ac06..c9948983a 100644 --- a/node/sync/syncer.go +++ b/node/sync/syncer.go @@ -196,3 +196,11 @@ func (s *Syncer) ReadL1MessagesInRange(start, end uint64) []types.L1Message { func (s *Syncer) LatestSynced() uint64 { return s.latestSynced } + +// L1Client returns the underlying L1 client (for sharing with other services) +func (s *Syncer) L1Client() *ethclient.Client { + if s.bridgeClient != nil { + return s.bridgeClient.L1Client() + } + return nil +} diff --git a/node/types/blob.go b/node/types/blob.go index d4aea96fb..49ac5dc64 100644 --- a/node/types/blob.go +++ b/node/types/blob.go @@ -153,11 +153,11 @@ func DecodeTxsFromBytes(txsBytes []byte) (eth.Transactions, error) { return nil, err } innerTx = new(eth.SetCodeTx) - case eth.AltFeeTxType: + case eth.MorphTxType: if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { return nil, err } - innerTx = new(eth.AltFeeTx) + innerTx = new(eth.MorphTx) default: if firstByte <= 0xf7 { // legacy tx first byte must be greater than 0xf7(247) return nil, fmt.Errorf("not supported tx type: %d", firstByte) diff --git a/node/types/retryable_client.go b/node/types/retryable_client.go index 899c3be12..263f94fc5 100644 --- a/node/types/retryable_client.go +++ b/node/types/retryable_client.go @@ -54,8 +54,8 @@ type forkConfig struct { // morphExtension contains Morph-specific configuration fields type morphExtension struct { - UseZktrie bool `json:"useZktrie"` - MPTForkTime *uint64 `json:"mptForkTime,omitempty"` + UseZktrie bool `json:"useZktrie"` + JadeForkTime *uint64 `json:"jadeForkTime,omitempty"` } // GethConfig holds the configuration fetched from geth via eth_config API @@ -105,28 +105,28 @@ func FetchGethConfig(rpcURL string, logger tmlog.Logger) (*GethConfig, error) { logger.Info("Fetched useZktrie from geth", "useZktrie", config.UseZktrie) } - // Try to get mptForkTime from current config - if resp.Current != nil && resp.Current.Morph != nil && resp.Current.Morph.MPTForkTime != nil { - config.SwitchTime = *resp.Current.Morph.MPTForkTime - logger.Info("Fetched MPT fork time from geth", "mptForkTime", config.SwitchTime, "source", "current") + // Try to get jadeForkTime from current config + if resp.Current != nil && resp.Current.Morph != nil && resp.Current.Morph.JadeForkTime != nil { + config.SwitchTime = *resp.Current.Morph.JadeForkTime + logger.Info("Fetched Jade fork time from geth", "jadeForkTime", config.SwitchTime, "source", "current") return config, nil } // Fallback to next config - if resp.Next != nil && resp.Next.Morph != nil && resp.Next.Morph.MPTForkTime != nil { - config.SwitchTime = *resp.Next.Morph.MPTForkTime - logger.Info("Fetched MPT fork time from geth", "mptForkTime", config.SwitchTime, "source", "next") + if resp.Next != nil && resp.Next.Morph != nil && resp.Next.Morph.JadeForkTime != nil { + config.SwitchTime = *resp.Next.Morph.JadeForkTime + logger.Info("Fetched Jade fork time from geth", "jadeForkTime", config.SwitchTime, "source", "next") return config, nil } // Fallback to last config - if resp.Last != nil && resp.Last.Morph != nil && resp.Last.Morph.MPTForkTime != nil { - config.SwitchTime = *resp.Last.Morph.MPTForkTime - logger.Info("Fetched MPT fork time from geth", "mptForkTime", config.SwitchTime, "source", "last") + if resp.Last != nil && resp.Last.Morph != nil && resp.Last.Morph.JadeForkTime != nil { + config.SwitchTime = *resp.Last.Morph.JadeForkTime + logger.Info("Fetched Jade fork time from geth", "jadeForkTime", config.SwitchTime, "source", "last") return config, nil } - logger.Info("MPT fork time not configured in geth, switch disabled") + logger.Info("Jade fork time not configured in geth, switch disabled") return config, nil } @@ -141,9 +141,9 @@ type RetryableClient struct { logger tmlog.Logger } -// MPTForkTime returns the configured MPT fork/switch timestamp fetched from geth (eth_config). +// JadeForkTime returns the configured Jade fork/switch timestamp fetched from geth (eth_config). // Note: this is a local value stored in the client; it does not perform any RPC. -func (rc *RetryableClient) MPTForkTime() uint64 { +func (rc *RetryableClient) JadeForkTime() uint64 { return rc.switchTime } @@ -428,7 +428,25 @@ func (rc *RetryableClient) HeaderByNumber(ctx context.Context, blockNumber *big. if retryErr := backoff.Retry(func() error { resp, respErr := rc.eClient().HeaderByNumber(ctx, blockNumber) if respErr != nil { - rc.logger.Info("failed to call BlockNumber", "error", respErr) + rc.logger.Info("failed to call HeaderByNumber", "error", respErr) + if retryableError(respErr) { + return respErr + } + err = respErr + } + ret = resp + return nil + }, rc.b); retryErr != nil { + return nil, retryErr + } + return +} + +func (rc *RetryableClient) BlockByNumber(ctx context.Context, blockNumber *big.Int) (ret *eth.Block, err error) { + if retryErr := backoff.Retry(func() error { + resp, respErr := rc.ethClient.BlockByNumber(ctx, blockNumber) + if respErr != nil { + rc.logger.Info("failed to call BlockByNumber", "error", respErr) if retryableError(respErr) { return respErr } @@ -506,3 +524,26 @@ func retryableError(err error) bool { // strings.Contains(err.Error(), Timeout) return !strings.Contains(err.Error(), DiscontinuousBlockError) } + +// ============================================================================ +// L2NodeV2 methods for sequencer mode +// ============================================================================ + +// AssembleL2BlockV2 assembles a L2 block based on parent hash. +func (rc *RetryableClient) AssembleL2BlockV2(ctx context.Context, parentHash common.Hash, transactions eth.Transactions) (ret *catalyst.ExecutableL2Data, err error) { + if retryErr := backoff.Retry(func() error { + resp, respErr := rc.authClient.AssembleL2BlockV2(ctx, parentHash, transactions) + if respErr != nil { + rc.logger.Info("failed to AssembleL2BlockV2", "error", respErr) + if retryableError(respErr) { + return respErr + } + err = respErr + } + ret = resp + return nil + }, rc.b); retryErr != nil { + return nil, retryErr + } + return +} diff --git a/ops/devnet-morph/devnet/__init__.py b/ops/devnet-morph/devnet/__init__.py index 4ec81c897..385a7a2a3 100644 --- a/ops/devnet-morph/devnet/__init__.py +++ b/ops/devnet-morph/devnet/__init__.py @@ -248,6 +248,7 @@ def devnet_deploy(paths, args): env_data['MORPH_ROLLUP'] = addresses['Proxy__Rollup'] env_data['RUST_LOG'] = rust_log_level env_data['Proxy__L1Staking'] = addresses['Proxy__L1Staking'] + env_data['L1_SEQUENCER_CONTRACT'] = addresses.get('Proxy__L1Sequencer', '') envfile.seek(0) for key, value in env_data.items(): envfile.write(f'{key}={value}\n') diff --git a/ops/docker-sequencer-test/Dockerfile.l2-geth-test b/ops/docker-sequencer-test/Dockerfile.l2-geth-test new file mode 100644 index 000000000..1c053f44b --- /dev/null +++ b/ops/docker-sequencer-test/Dockerfile.l2-geth-test @@ -0,0 +1,26 @@ +# Build Geth for Sequencer Test +# Build context should be bitget/ (parent of morph) +FROM ghcr.io/morph-l2/go-ubuntu-builder:go-1.24-ubuntu AS builder + +# Copy local go-ethereum (not submodule) +COPY ./go-ethereum /go-ethereum +WORKDIR /go-ethereum + +# Build geth +RUN go run build/ci.go install ./cmd/geth + +# Runtime stage +FROM ghcr.io/morph-l2/go-ubuntu-builder:go-1.24-ubuntu + +RUN apt-get -qq update && apt-get -qq install -y --no-install-recommends \ + ca-certificates bash curl \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ +COPY ./morph/ops/docker-sequencer-test/entrypoint-l2.sh /entrypoint.sh + +VOLUME ["/db"] + +ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] + +EXPOSE 8545 8546 8551 30303 30303/udp diff --git a/ops/docker-sequencer-test/Dockerfile.l2-node-test b/ops/docker-sequencer-test/Dockerfile.l2-node-test new file mode 100644 index 000000000..1ece1eb81 --- /dev/null +++ b/ops/docker-sequencer-test/Dockerfile.l2-node-test @@ -0,0 +1,47 @@ +# Build Stage +FROM ghcr.io/morph-l2/go-ubuntu-builder:go-1.24-ubuntu AS builder + +# First: Copy only go.mod/go.sum files to cache dependencies +# Order matters for cache efficiency + +# Copy go-ethereum dependency files +COPY ./go-ethereum/go.mod ./go-ethereum/go.sum /bitget/go-ethereum/ + +# Copy tendermint dependency files +COPY ./tendermint/go.mod ./tendermint/go.sum /bitget/tendermint/ + +# Copy morph go.work and all module dependency files +COPY ./morph/go.work ./morph/go.work.sum /bitget/morph/ +COPY ./morph/node/go.mod ./morph/node/go.sum /bitget/morph/node/ +COPY ./morph/bindings/go.mod ./morph/bindings/go.sum /bitget/morph/bindings/ +COPY ./morph/contracts/go.mod ./morph/contracts/go.sum /bitget/morph/contracts/ +COPY ./morph/oracle/go.mod ./morph/oracle/go.sum /bitget/morph/oracle/ +COPY ./morph/tx-submitter/go.mod ./morph/tx-submitter/go.sum /bitget/morph/tx-submitter/ +COPY ./morph/ops/l2-genesis/go.mod ./morph/ops/l2-genesis/go.sum /bitget/morph/ops/l2-genesis/ +COPY ./morph/ops/tools/go.mod ./morph/ops/tools/go.sum /bitget/morph/ops/tools/ +COPY ./morph/token-price-oracle/go.mod ./morph/token-price-oracle/go.sum /bitget/morph/token-price-oracle/ + +# Download dependencies (this layer is cached if go.mod/go.sum don't change) +WORKDIR /bitget/morph/node +RUN go mod download -x + +# Now copy all source code +COPY ./go-ethereum /bitget/go-ethereum +COPY ./tendermint /bitget/tendermint +COPY ./morph /bitget/morph + +# Build (no need to download again, just compile) +WORKDIR /bitget/morph/node +RUN make build + +# Final Stage +FROM ghcr.io/morph-l2/go-ubuntu-builder:go-1.24-ubuntu + +RUN apt-get -qq update \ + && apt-get -qq install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /bitget/morph/node/build/bin/tendermint /usr/local/bin/ +COPY --from=builder /bitget/morph/node/build/bin/morphnode /usr/local/bin/ + +CMD ["morphnode", "--home", "/data"] diff --git a/ops/docker-sequencer-test/README.md b/ops/docker-sequencer-test/README.md new file mode 100644 index 000000000..156f2d64a --- /dev/null +++ b/ops/docker-sequencer-test/README.md @@ -0,0 +1 @@ +This directory is intended for Docker testing purposes only and may be removed in the future. \ No newline at end of file diff --git a/ops/docker-sequencer-test/docker-compose.override.yml b/ops/docker-sequencer-test/docker-compose.override.yml new file mode 100644 index 000000000..9cc69cae8 --- /dev/null +++ b/ops/docker-sequencer-test/docker-compose.override.yml @@ -0,0 +1,65 @@ +# Override file to use test images +# Copy this to ops/docker/docker-compose.override.yml before running test +version: '3.8' + +services: + morph-geth-0: + image: morph-geth-test:latest + build: + context: ../.. + dockerfile: ops/docker-sequencer-test/Dockerfile.l2-geth-test + + morph-geth-1: + image: morph-geth-test:latest + + morph-geth-2: + image: morph-geth-test:latest + + morph-geth-3: + image: morph-geth-test:latest + + node-0: + image: morph-node-test:latest + build: + context: ../.. + dockerfile: ops/docker-sequencer-test/Dockerfile.l2-node-test + environment: + - MORPH_NODE_SEQUENCER_PRIVATE_KEY=0xd99870855d97327d20c666abc78588f1449b1fac76ed0c86c1afb9ce2db85f32 + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + + + node-1: + image: morph-node-test:latest + environment: + - MORPH_NODE_SEQUENCER_PRIVATE_KEY=0x0890c388c3bf5e04fee1d8f3c117e5f44f435ced7baf7bfd66c10e1f3a3f4b10 + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + + + node-2: + image: morph-node-test:latest + environment: + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + + + node-3: + image: morph-node-test:latest + environment: + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + + + sentry-geth-0: + image: morph-geth-test:latest + + sentry-node-0: + image: morph-node-test:latest + environment: + - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + diff --git a/ops/docker-sequencer-test/entrypoint-l2.sh b/ops/docker-sequencer-test/entrypoint-l2.sh new file mode 100644 index 000000000..04dfed476 --- /dev/null +++ b/ops/docker-sequencer-test/entrypoint-l2.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +GETH_DATA_DIR=${GETH_DATA_DIR:-/db} +GENESIS_FILE_PATH=${GENESIS_FILE_PATH:-/genesis.json} +JWT_SECRET_PATH=${JWT_SECRET_PATH:-/jwt-secret.txt} + +# Initialize geth if not already done +if [ ! -d "$GETH_DATA_DIR/geth/chaindata" ]; then + echo "Initializing geth with genesis file..." + geth init --datadir "$GETH_DATA_DIR" "$GENESIS_FILE_PATH" +fi + +echo "Starting geth..." +exec geth \ + --datadir "$GETH_DATA_DIR" \ + --http \ + --http.addr "0.0.0.0" \ + --http.port 8545 \ + --http.api "eth,net,web3,debug,txpool,engine" \ + --http.corsdomain "*" \ + --http.vhosts "*" \ + --ws \ + --ws.addr "0.0.0.0" \ + --ws.port 8546 \ + --ws.api "eth,net,web3,debug,txpool,engine" \ + --ws.origins "*" \ + --authrpc.addr "0.0.0.0" \ + --authrpc.port 8551 \ + --authrpc.vhosts "*" \ + --authrpc.jwtsecret "$JWT_SECRET_PATH" \ + --networkid 53077 \ + --nodiscover \ + --syncmode full \ + --gcmode archive \ + --metrics \ + --metrics.addr "0.0.0.0" \ + --pprof \ + --pprof.addr "0.0.0.0" \ + --verbosity 3 \ + "$@" + diff --git a/ops/docker-sequencer-test/run-test.sh b/ops/docker-sequencer-test/run-test.sh new file mode 100755 index 000000000..81361fefa --- /dev/null +++ b/ops/docker-sequencer-test/run-test.sh @@ -0,0 +1,549 @@ +#!/bin/bash +# Sequencer Upgrade Test Runner +# Reuses devnet-morph logic but with test-specific docker images + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MORPH_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +BITGET_ROOT="$(cd "$MORPH_ROOT/.." && pwd)" +OPS_DIR="$MORPH_ROOT/ops" +DOCKER_DIR="$OPS_DIR/docker" +DEVNET_DIR="$OPS_DIR/devnet-morph" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Configuration +UPGRADE_HEIGHT=${UPGRADE_HEIGHT:-10} +L2_RPC="http://127.0.0.1:8545" +L2_RPC_NODE1="http://127.0.0.1:8645" + +# ========== Helper Functions ========== + +wait_for_rpc() { + local rpc_url="$1" + local max_retries=${2:-60} + local retry=0 + + log_info "Waiting for RPC at $rpc_url..." + while [ $retry -lt $max_retries ]; do + if curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + "$rpc_url" 2>/dev/null | grep -q "result"; then + log_success "RPC is ready!" + return 0 + fi + retry=$((retry + 1)) + sleep 2 + done + log_error "Timeout waiting for RPC" + return 1 +} + +get_block_number() { + local rpc_url="${1:-$L2_RPC}" + local result + result=$(curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + "$rpc_url" 2>/dev/null) + echo "$result" | grep -o '"result":"[^"]*"' | cut -d'"' -f4 | xargs printf "%d" 2>/dev/null || echo "0" +} + +wait_for_block() { + local target_height=$1 + local rpc_url="${2:-$L2_RPC}" + + log_info "Waiting for block $target_height..." + while true; do + local current=$(get_block_number "$rpc_url") + if [ "$current" -ge "$target_height" ]; then + log_success "Reached block $current" + return 0 + fi + echo -ne "\r Current block: $current / $target_height" + sleep 2 + done +} + +# ========== Setup Functions ========== + +# Export consensus switch height as environment variable for Docker containers +# The morphnode binary reads MORPH_NODE_CONSENSUS_SWITCH_HEIGHT at runtime +set_upgrade_height() { + local height=$1 + log_info "Setting consensus switch height to $height (via CONSENSUS_SWITCH_HEIGHT env)..." + export CONSENSUS_SWITCH_HEIGHT="$height" + log_success "CONSENSUS_SWITCH_HEIGHT=$height (will be passed to containers)" +} + +# Build test images (with -test suffix) +# Uses bitget/ as build context to access local go-ethereum and tendermint +build_test_images() { + log_info "Building test Docker images..." + log_info "Using build context: $BITGET_ROOT" + + # Build go-ubuntu-builder if needed + cd "$MORPH_ROOT" + make go-ubuntu-builder + + # Build from bitget/ directory to access all repos + cd "$BITGET_ROOT" + + # # Copy go module cache to avoid network downloads + # if [ -d "$HOME/go/pkg/mod" ]; then + # log_info "Copying go module cache to build context..." + # rm -rf .gomodcache + # cp -r "$HOME/go/pkg/mod" .gomodcache + # else + # log_warn "Go module cache not found at $HOME/go/pkg/mod" + # log_warn "Build may fail due to network issues" + # fi + + # Build test geth image + log_info "Building morph-geth-test (using local go-ethereum)..." + docker build -t morph-geth-test:latest \ + -f morph/ops/docker-sequencer-test/Dockerfile.l2-geth-test . + + # Build test node image + log_info "Building morph-node-test (using local go-ethereum + tendermint)..." + docker build -t morph-node-test:latest \ + -f morph/ops/docker-sequencer-test/Dockerfile.l2-node-test . + + # # Cleanup go module cache copy + # rm -rf .gomodcache + + log_success "Test images built!" +} + +# Run full devnet setup (reusing existing logic, but skip L2 startup) +setup_devnet() { + log_info "Running devnet setup..." + cd "$MORPH_ROOT" + + # Note: upgrade height should already be set before build_test_images + + # Step 1: Start L1 and setup tendermint nodes + # Note: main.py calls setup_devnet_nodes() before devnet.main() + log_info "Step 1: Starting L1 and setting up tendermint nodes..." + python3 "$DEVNET_DIR/main.py" --polyrepo-dir="$MORPH_ROOT" --only-l1 + + # Step 2: Deploy contracts and generate L2 genesis (without starting L2) + log_info "Step 2: Deploying contracts and generating L2 genesis..." + python3 -c " +import sys +import os +import time +import re +import fileinput +sys.path.insert(0, '$DEVNET_DIR') +import devnet +from devnet import run_command, read_json, write_json, test_port, log + +pjoin = os.path.join +polyrepo_dir = '$MORPH_ROOT' +L2_dir = pjoin(polyrepo_dir, 'ops', 'l2-genesis') +devnet_dir = pjoin(polyrepo_dir, 'ops', 'l2-genesis', '.devnet') +ops_dir = pjoin(polyrepo_dir, 'ops', 'docker') +contracts_dir = pjoin(polyrepo_dir, 'contracts') + +os.makedirs(devnet_dir, exist_ok=True) + +# Generate network config +devnet_cfg_orig = pjoin(L2_dir, 'deploy-config', 'devnet-deploy-config.json') +deploy_config = read_json(devnet_cfg_orig) +deploy_config['l1GenesisBlockTimestamp'] = '0x{:x}'.format(int(time.time())) +deploy_config['l1StartingBlockTag'] = 'earliest' +temp_deploy_config = pjoin(devnet_dir, 'deploy-config.json') +write_json(temp_deploy_config, deploy_config) + +# Deploy L1 contracts +deployment_dir = pjoin(devnet_dir, 'devnetL1.json') +run_command(['rm', '-f', deployment_dir], env={}, cwd=contracts_dir) +log.info('Deploying L1 Proxy contracts...') +run_command(['yarn', 'build'], env={}, cwd=contracts_dir) +run_command(['npx', 'hardhat', 'deploy', '--network', 'l1', '--storagepath', deployment_dir, '--concurrent', 'true'], env={}, cwd=contracts_dir) + +# Generate L2 genesis +log.info('Generating L2 genesis and rollup configs...') +run_command([ + 'env', 'CGO_ENABLED=1', 'CGO_LDFLAGS=\"-ldl\"', + 'go', 'run', 'cmd/main.go', 'genesis', 'l2', + '--l1-rpc', 'http://localhost:9545', + '--deploy-config', temp_deploy_config, + '--deployment-dir', deployment_dir, + '--outfile.l2', pjoin(devnet_dir, 'genesis-l2.json'), + '--outfile.genbatchheader', pjoin(devnet_dir, 'genesis-batch-header.json'), + '--outfile.rollup', pjoin(devnet_dir, 'rollup.json') +], cwd=L2_dir) + +# Initialize contracts +log.info('Deploying L1 Impl contracts and initialize...') +rollup_cfg = read_json(pjoin(devnet_dir, 'rollup.json')) +genesis_batch_header = rollup_cfg['genesis_batch_header'] +contracts_config = pjoin(contracts_dir, 'src', 'deploy-config', 'l1.ts') +pattern3 = re.compile(\"batchHeader: '.*'\") +for line in fileinput.input(contracts_config, inplace=True): + modified_line = re.sub(pattern3, f\"batchHeader: '{genesis_batch_header}'\", line) + print(modified_line, end='') +run_command(['npx', 'hardhat', 'initialize', '--network', 'l1', '--storagepath', deployment_dir, '--concurrent', 'true'], env={}, cwd=contracts_dir) + +# Staking +log.info('Staking sequencers...') +addresses = {} +deployment = read_json(deployment_dir) +for d in deployment: + addresses[d['name']] = d['address'] +for i in range(4): + run_command(['cast', 'send', addresses['Proxy__L1Staking'], + 'register(bytes32,bytes memory)', + deploy_config['l2StakingTmKeys'][i], + deploy_config['l2StakingBlsKeys'][i], + '--rpc-url', 'http://127.0.0.1:9545', + '--value', '1ether', + '--private-key', deploy_config['l2StakingPks'][i] + ]) + +# Update .env file +log.info('Updating .env file...') +env_file = pjoin(ops_dir, '.env') +env_data = {} +with open(env_file, 'r+') as envfile: + env_content = envfile.readlines() + for line in env_content: + line = line.strip() + if line and not line.startswith('#'): + parts = line.split('=', 1) + if len(parts) == 2: + env_data[parts[0].strip()] = parts[1].strip() + env_data['L1_CROSS_DOMAIN_MESSENGER'] = addresses['Proxy__L1CrossDomainMessenger'] + env_data['MORPH_PORTAL'] = addresses['Proxy__L1MessageQueueWithGasPriceOracle'] + env_data['MORPH_ROLLUP'] = addresses['Proxy__Rollup'] + env_data['MORPH_L1STAKING'] = addresses['Proxy__L1Staking'] + env_data['L1_SEQUENCER_CONTRACT'] = addresses.get('Proxy__L1Sequencer', '') + envfile.seek(0) + for key, value in env_data.items(): + envfile.write(f'{key}={value}\n') + envfile.truncate() + +log.info('Contract deployment and genesis generation complete!') +log.info('Skipping L2 startup - will be done with test images.') +" + + log_success "Devnet setup complete (L2 not started yet)" +} + +# Docker compose command with override file +# Note: -f must explicitly include override file when using non-default compose file name +COMPOSE_CMD="docker compose -f docker-compose-4nodes.yml -f docker-compose.override.yml" +COMPOSE_CMD_NO_OVERRIDE="docker compose -f docker-compose-4nodes.yml" + +# Copy override file to use test images +setup_override() { + log_info "Setting up docker-compose override for test images..." + cp "$SCRIPT_DIR/docker-compose.override.yml" "$DOCKER_DIR/docker-compose.override.yml" + log_success "Override file copied to $DOCKER_DIR/" +} + +# Remove override file +remove_override() { + rm -f "$DOCKER_DIR/docker-compose.override.yml" +} + +# Start L2 with test images +start_l2_test() { + log_info "Starting L2 with test images..." + cd "$DOCKER_DIR" + + # Setup override file + setup_override + + # Read the .env file to get contract addresses + source .env 2>/dev/null || true + + # Set sequencer private key + export SEQUENCER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + + # Stop any existing L2 containers + $COMPOSE_CMD stop \ + morph-geth-0 morph-geth-1 morph-geth-2 morph-geth-3 \ + node-0 node-1 node-2 node-3 2>/dev/null || true + + # Note: Test images should already be built by build_test_images() + # Uncomment below if you need to rebuild during start + # log_info "Building L2 containers with test images..." + # $COMPOSE_CMD build morph-geth-0 node-0 + + # Start L2 geth nodes + log_info "Starting L2 geth nodes..." + $COMPOSE_CMD up -d morph-geth-0 morph-geth-1 morph-geth-2 morph-geth-3 + + sleep 5 + + # Start L2 tendermint nodes + log_info "Starting L2 tendermint nodes..." + $COMPOSE_CMD up -d node-0 node-1 node-2 node-3 + + wait_for_rpc "$L2_RPC" + log_success "L2 is running with test images!" +} + +# ========== Test Functions ========== + +test_pbft_mode() { + log_info "========== Phase 1: Testing PBFT Mode ==========" + + local start_block=$(get_block_number) + log_info "Starting block: $start_block" + + # Wait for some blocks + local target=$((start_block + 10)) + wait_for_block $target + + # Verify nodes in sync + local block0=$(get_block_number "$L2_RPC") + local block1=$(get_block_number "$L2_RPC_NODE1") + + local diff=$((block0 - block1)) + if [ ${diff#-} -le 2 ]; then + log_success "Nodes in sync (node0: $block0, node1: $block1)" + else + log_error "Nodes out of sync!" + return 1 + fi +} + +test_upgrade() { + log_info "========== Phase 2: Waiting for Upgrade ==========" + log_info "Upgrade height: $UPGRADE_HEIGHT" + + wait_for_block $UPGRADE_HEIGHT + sleep 10 + + # Verify network continues + local post_upgrade=$(get_block_number) + wait_for_block $((post_upgrade + 5)) + + log_success "Upgrade completed! Network continues producing blocks." +} + +test_sequencer_mode() { + log_info "========== Phase 3: Testing Sequencer Mode ==========" + + local start_block=$(get_block_number) + wait_for_block $((start_block + 20)) + + local block0=$(get_block_number "$L2_RPC") + local block1=$(get_block_number "$L2_RPC_NODE1") + + local diff=$((block0 - block1)) + if [ ${diff#-} -le 2 ]; then + log_success "Nodes in sync after upgrade (node0: $block0, node1: $block1)" + else + log_error "Nodes out of sync after upgrade!" + return 1 + fi +} + +test_fullnode_sync() { + log_info "========== Phase 4: Testing Fullnode Sync ==========" + + local current_height=$(get_block_number) + log_info "Current height: $current_height" + + cd "$DOCKER_DIR" + + # Start sentry node (fullnode) + log_info "Starting fullnode (sentry-node-0)..." + $COMPOSE_CMD up -d sentry-geth-0 sentry-node-0 + + sleep 10 + wait_for_rpc "http://127.0.0.1:8945" + + # Wait for sync + local target_sync=$((current_height - 5)) + local max_wait=300 + local waited=0 + + while [ $waited -lt $max_wait ]; do + local fn_block=$(get_block_number "http://127.0.0.1:8945") + if [ "$fn_block" -ge "$target_sync" ]; then + log_success "Fullnode synced to block $fn_block" + return 0 + fi + echo -ne "\r Fullnode: $fn_block / $target_sync" + sleep 5 + waited=$((waited + 5)) + done + + log_error "Fullnode sync timeout" + return 1 +} + +# ========== Transaction Generator ========== + +start_tx_generator() { + log_info "Starting transaction generator..." + + # Simple tx generator using cast + ( + while true; do + RANDOM_ADDR="0x$(openssl rand -hex 20)" + cast send --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --rpc-url "$L2_RPC" \ + --value 1wei \ + "$RANDOM_ADDR" 2>/dev/null || true + sleep ${TX_INTERVAL:-5} + done + ) & + TX_GEN_PID=$! + log_info "TX generator started (PID: $TX_GEN_PID)" +} + +stop_tx_generator() { + if [ -n "$TX_GEN_PID" ]; then + kill $TX_GEN_PID 2>/dev/null || true + log_info "TX generator stopped" + fi +} + +# ========== Cleanup ========== + +cleanup() { + log_info "Cleaning up..." + stop_tx_generator + cd "$DOCKER_DIR" + $COMPOSE_CMD_NO_OVERRIDE down -v 2>/dev/null || true + remove_override +} + +# ========== Main Commands ========== + +run_full_test() { + log_info "==========================================" + log_info " Sequencer Upgrade Test" + log_info " Upgrade Height: $UPGRADE_HEIGHT" + log_info "==========================================" + + trap cleanup EXIT + + # Set upgrade height BEFORE building (so it's compiled into the binary) + set_upgrade_height "$UPGRADE_HEIGHT" + + # Build test images (now with correct upgrade height) + build_test_images + + # Setup devnet (L1 + contracts + L2 genesis) + setup_devnet + + # Start L2 with test images + start_l2_test + + # Start tx generator + start_tx_generator + + # Run tests + test_pbft_mode + test_upgrade + test_sequencer_mode + test_fullnode_sync + + stop_tx_generator + + log_success "==========================================" + log_success " ALL TESTS PASSED!" + log_success "==========================================" +} + +show_status() { + echo "Node 1: Block $(get_block_number http://127.0.0.1:8645)" + echo "Node 2: Block $(get_block_number http://127.0.0.1:8745)" + echo "Node 3: Block $(get_block_number http://127.0.0.1:8845)" + echo "Node 0 (seq-0): Block $(get_block_number http://127.0.0.1:8545)" + echo "Sentry: Block $(get_block_number http://127.0.0.1:8945 2>/dev/null || echo 'N/A')" +} + +show_logs() { + cd "$DOCKER_DIR" + $COMPOSE_CMD_NO_OVERRIDE logs -f "$@" +} + +# ========== Command Parsing ========== + +case "${1:-}" in + build) + build_test_images + ;; + setup) + setup_devnet + ;; + start) + start_l2_test + ;; + stop) + cd "$DOCKER_DIR" + $COMPOSE_CMD_NO_OVERRIDE down + ;; + clean) + cleanup + # Also clean L2 genesis + rm -rf "$OPS_DIR/l2-genesis/.devnet" + rm -rf "$DOCKER_DIR/.devnet" + ;; + logs) + shift + show_logs "$@" + ;; + test) + run_full_test + ;; + tx) + start_tx_generator + wait + ;; + status) + show_status + ;; + upgrade-height) + set_upgrade_height "${2:-50}" + ;; + *) + echo "Sequencer Upgrade Test Runner" + echo "" + echo "Usage: $0 {build|setup|start|stop|clean|logs|test|tx|status|upgrade-height}" + echo "" + echo "Commands:" + echo " build - Build test Docker images (morph-geth-test, morph-node-test)" + echo " setup - Run full devnet setup (L1 + contracts + L2 genesis)" + echo " start - Start L2 nodes with test images" + echo " stop - Stop all containers" + echo " clean - Stop and remove all containers and data" + echo " logs [service] - Show container logs" + echo " test - Run full upgrade test" + echo " tx - Start transaction generator" + echo " status - Show current block numbers" + echo " upgrade-height N - Set upgrade height to N" + echo "" + echo "Environment Variables:" + echo " UPGRADE_HEIGHT - Block height for consensus switch (default: 10)" + echo " TX_INTERVAL - Seconds between txs (default: 5)" + echo "" + echo "Test Flow:" + echo " 1. build - Build test images" + echo " 2. setup - Deploy L1, contracts, generate L2 genesis" + echo " 3. start - Start L2 with test images" + echo " 4. test - Run PBFT -> Upgrade -> Sequencer -> Fullnode tests" + echo "" + echo "Quick Start:" + echo " UPGRADE_HEIGHT=10 $0 test" + ;; +esac diff --git a/ops/docker-sequencer-test/scripts/tx-generator.sh b/ops/docker-sequencer-test/scripts/tx-generator.sh new file mode 100644 index 000000000..2311a64d5 --- /dev/null +++ b/ops/docker-sequencer-test/scripts/tx-generator.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# Transaction Generator for Sequencer Test +# Sends random transactions to keep the network active + +set -e + +L2_RPC="${L2_RPC:-http://morph-geth-0:8545}" +INTERVAL="${TX_INTERVAL:-5}" # seconds between txs +PRIVATE_KEY="${PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}" + +# Wait for L2 to be ready +echo "Waiting for L2 RPC to be ready..." +while true; do + if curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + "$L2_RPC" | grep -q "result"; then + echo "L2 RPC is ready!" + break + fi + echo "Waiting..." + sleep 2 +done + +# Get initial nonce +get_nonce() { + curl -s -X POST -H "Content-Type: application/json" \ + --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\"latest\"],\"id\":1}" \ + "$L2_RPC" | grep -o '"result":"[^"]*"' | cut -d'"' -f4 +} + +# Get current block number +get_block_number() { + curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + "$L2_RPC" | grep -o '"result":"[^"]*"' | cut -d'"' -f4 +} + +echo "Starting transaction generator..." +echo "L2 RPC: $L2_RPC" +echo "Interval: ${INTERVAL}s" + +NONCE_HEX=$(get_nonce) +NONCE=$((NONCE_HEX)) +TX_COUNT=0 + +while true; do + BLOCK=$(get_block_number) + BLOCK_DEC=$((BLOCK)) + + # Generate random recipient address + RANDOM_SUFFIX=$(od -An -N4 -tx1 /dev/urandom | tr -d ' ') + TO_ADDR="0x000000000000000000000000000000${RANDOM_SUFFIX}" + + # Create and send transaction + NONCE_HEX=$(printf "0x%x" $NONCE) + TX_DATA="{\"jsonrpc\":\"2.0\",\"method\":\"eth_sendTransaction\",\"params\":[{\"from\":\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\"to\":\"${TO_ADDR}\",\"value\":\"0x1\",\"nonce\":\"${NONCE_HEX}\"}],\"id\":1}" + + RESULT=$(curl -s -X POST -H "Content-Type: application/json" --data "$TX_DATA" "$L2_RPC") + + if echo "$RESULT" | grep -q "result"; then + TX_HASH=$(echo "$RESULT" | grep -o '"result":"[^"]*"' | cut -d'"' -f4) + echo "[Block $BLOCK_DEC] TX #$TX_COUNT sent: $TX_HASH" + NONCE=$((NONCE + 1)) + TX_COUNT=$((TX_COUNT + 1)) + else + echo "[Block $BLOCK_DEC] TX failed: $RESULT" + # Refresh nonce in case of error + NONCE_HEX=$(get_nonce) + NONCE=$((NONCE_HEX)) + fi + + sleep "$INTERVAL" +done + diff --git a/ops/l2-genesis/go.mod b/ops/l2-genesis/go.mod index e464485c4..f2a1a0225 100644 --- a/ops/l2-genesis/go.mod +++ b/ops/l2-genesis/go.mod @@ -2,11 +2,11 @@ module morph-l2/morph-deployer go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( github.com/holiman/uint256 v1.2.4 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.17 ) diff --git a/ops/l2-genesis/go.sum b/ops/l2-genesis/go.sum index 6907ac512..bfaff71dd 100644 --- a/ops/l2-genesis/go.sum +++ b/ops/l2-genesis/go.sum @@ -141,8 +141,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.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/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= diff --git a/ops/tools/go.mod b/ops/tools/go.mod index 78ad604a6..f29d98523 100644 --- a/ops/tools/go.mod +++ b/ops/tools/go.mod @@ -2,10 +2,10 @@ module morph-l2/tools go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/tendermint/tendermint v0.35.9 ) diff --git a/ops/tools/go.sum b/ops/tools/go.sum index 6be6c6bf0..5dae91d8e 100644 --- a/ops/tools/go.sum +++ b/ops/tools/go.sum @@ -163,10 +163,10 @@ 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.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= -github.com/morph-l2/tendermint v0.3.3 h1:zsmzVJfKp+NuCr45ZUUY2ZJjnHAVLzwJLID6GxBR4i4= -github.com/morph-l2/tendermint v0.3.3/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 h1:bnPBsPMWnN0bHhWvMMFp6dp0RB9Z9P1NuKGvFwu8V7E= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= 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= diff --git a/oracle/go.mod b/oracle/go.mod index d82290072..2de8dbcfb 100644 --- a/oracle/go.mod +++ b/oracle/go.mod @@ -2,12 +2,12 @@ module morph-l2/oracle go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( github.com/go-kit/kit v0.12.0 github.com/morph-l2/externalsign v0.3.1 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/prometheus/client_golang v1.17.0 github.com/stretchr/testify v1.10.0 github.com/tendermint/tendermint v0.35.9 diff --git a/oracle/go.sum b/oracle/go.sum index da0d78add..e1a059288 100644 --- a/oracle/go.sum +++ b/oracle/go.sum @@ -174,10 +174,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/externalsign v0.3.1 h1:UYFDZFB0L85A4rDvuwLNBiGEi0kSmg9AZ2v8Q5O4dQo= github.com/morph-l2/externalsign v0.3.1/go.mod h1:b6NJ4GUiiG/gcSJsp3p8ExsIs4ZdphlrVALASnVoGJE= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= -github.com/morph-l2/tendermint v0.3.3 h1:zsmzVJfKp+NuCr45ZUUY2ZJjnHAVLzwJLID6GxBR4i4= -github.com/morph-l2/tendermint v0.3.3/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 h1:bnPBsPMWnN0bHhWvMMFp6dp0RB9Z9P1NuKGvFwu8V7E= +github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2/go.mod h1:TtCzp9l6Z6yDUiwv3TbqKqw8Q8RKp3fSz5+adO1/Y8w= 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= diff --git a/token-price-oracle/go.mod b/token-price-oracle/go.mod index 02292b9e8..4e96710b3 100644 --- a/token-price-oracle/go.mod +++ b/token-price-oracle/go.mod @@ -9,7 +9,7 @@ replace ( require ( github.com/morph-l2/externalsign v0.3.1 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/prometheus/client_golang v1.17.0 github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli v1.22.17 diff --git a/token-price-oracle/go.sum b/token-price-oracle/go.sum index a0b161877..62a306f4c 100644 --- a/token-price-oracle/go.sum +++ b/token-price-oracle/go.sum @@ -147,8 +147,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/externalsign v0.3.1 h1:UYFDZFB0L85A4rDvuwLNBiGEi0kSmg9AZ2v8Q5O4dQo= github.com/morph-l2/externalsign v0.3.1/go.mod h1:b6NJ4GUiiG/gcSJsp3p8ExsIs4ZdphlrVALASnVoGJE= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/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= diff --git a/tx-submitter/go.mod b/tx-submitter/go.mod index ead642c78..6585ce2ac 100644 --- a/tx-submitter/go.mod +++ b/tx-submitter/go.mod @@ -2,14 +2,14 @@ module morph-l2/tx-submitter go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.3 +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.4-0.20260226093240-9be76fe518c2 require ( github.com/consensys/gnark-crypto v0.16.0 github.com/crate-crypto/go-eth-kzg v1.4.0 github.com/holiman/uint256 v1.2.4 github.com/morph-l2/externalsign v0.3.1 - github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 + github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 github.com/prometheus/client_golang v1.17.0 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a diff --git a/tx-submitter/go.sum b/tx-submitter/go.sum index 725f75a79..7c0fcf0b2 100644 --- a/tx-submitter/go.sum +++ b/tx-submitter/go.sum @@ -163,8 +163,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/externalsign v0.3.1 h1:UYFDZFB0L85A4rDvuwLNBiGEi0kSmg9AZ2v8Q5O4dQo= github.com/morph-l2/externalsign v0.3.1/go.mod h1:b6NJ4GUiiG/gcSJsp3p8ExsIs4ZdphlrVALASnVoGJE= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141 h1:A8eygErKU6WKMipGWIemzwLeYkIGLd9yb/Ry3x+J9PQ= -github.com/morph-l2/go-ethereum v1.10.14-0.20260211074551-4f0f6e6bd141/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767 h1:f0M0nedO3QLiWAeG1pMLzm+o1cpAatUpLDj2oNAVIjk= +github.com/morph-l2/go-ethereum v1.10.14-0.20260302105006-9e26fa129767/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=