diff --git a/bindings/bindings/l1sequencer.go b/bindings/bindings/l1sequencer.go index 80110f03..844dadb8 100644 --- a/bindings/bindings/l1sequencer.go +++ b/bindings/bindings/l1sequencer.go @@ -29,10 +29,16 @@ var ( _ = abi.ConvertType ) +// L1SequencerHistoryRecord is an auto generated low-level Go binding around an user-defined struct. +type L1SequencerHistoryRecord struct { + StartL2Block uint64 + SequencerAddr common.Address +} + // 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", + ABI: "[{\"type\":\"function\",\"name\":\"activeHeight\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getSequencer\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getSequencerAt\",\"inputs\":[{\"name\":\"l2Height\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getSequencerHistory\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple[]\",\"internalType\":\"structL1Sequencer.HistoryRecord[]\",\"components\":[{\"name\":\"startL2Block\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sequencerAddr\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getSequencerHistoryLength\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"initializeHistory\",\"inputs\":[{\"name\":\"firstSequencer\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"upgradeL2Block\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"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\":\"sequencerHistory\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"startL2Block\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sequencerAddr\",\"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\"},{\"name\":\"startL2Block\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"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\"},{\"name\":\"startL2Block\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false}]", + Bin: "0x608060405234801561000f575f80fd5b506111968061001d5f395ff3fe608060405234801561000f575f80fd5b50600436106100cf575f3560e01c8063761a90fd1161007d578063f151ce9e11610058578063f151ce9e146101ee578063f198e27f14610201578063f2fde38b14610214575f80fd5b8063761a90fd146101aa5780638da5cb5b146101bd578063c4d66de8146101db575f80fd5b80636628aea1116100ad5780636628aea1146101435780636d8ce3d214610158578063715018a6146101a0575f80fd5b80633d5767ce146100d35780633ef5e8cc146100e95780634d96a90a14610116575b5f80fd5b6065546040519081526020015b60405180910390f35b6066546100fd9067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016100e0565b61011e610227565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e0565b61014b6102e9565b6040516100e09190610f9d565b61016b61016636600461100b565b610376565b6040805167ffffffffffffffff909316835273ffffffffffffffffffffffffffffffffffffffff9091166020830152016100e0565b6101a86103c2565b005b6101a86101b8366004611061565b6103d5565b60335473ffffffffffffffffffffffffffffffffffffffff1661011e565b6101a86101e9366004611092565b6106a7565b61011e6101fc3660046110b2565b6108ba565b6101a861020f366004611061565b610ab7565b6101a8610222366004611092565b610cb7565b6065545f90610297576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6e6f2073657175656e63657220636f6e6669677572656400000000000000000060448201526064015b60405180910390fd5b606580546102a7906001906110f8565b815481106102b7576102b7611111565b5f9182526020909120015468010000000000000000900473ffffffffffffffffffffffffffffffffffffffff16919050565b60606065805480602002602001604051908101604052809291908181526020015f905b8282101561036d575f848152602090819020604080518082019091529084015467ffffffffffffffff8116825268010000000000000000900473ffffffffffffffffffffffffffffffffffffffff168183015282526001909201910161030c565b50505050905090565b60658181548110610385575f80fd5b5f9182526020909120015467ffffffffffffffff8116915068010000000000000000900473ffffffffffffffffffffffffffffffffffffffff1682565b6103ca610d6e565b6103d35f610def565b565b6103dd610d6e565b73ffffffffffffffffffffffffffffffffffffffff821661045a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f696e76616c696420616464726573730000000000000000000000000000000000604482015260640161028e565b6065546104c3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f6e6f7420696e697469616c697a65640000000000000000000000000000000000604482015260640161028e565b606580546104d3906001906110f8565b815481106104e3576104e3611111565b5f9182526020909120015467ffffffffffffffff9081169082161161058a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f73746172744c32426c6f636b206d75737420626520677265617465722074686160448201527f6e206c617374207265636f726400000000000000000000000000000000000000606482015260840161028e565b606580545f919061059d906001906110f8565b815481106105ad576105ad611111565b5f9182526020808320919091015460408051808201825267ffffffffffffffff87811680835273ffffffffffffffffffffffffffffffffffffffff8a8116848801818152606580546001810182559a5294517f8ff97419363ffd7000167f130ef7168fbea05faf9251824ca5043f113cc6a7c790990180549551999094167fffffffff00000000000000000000000000000000000000000000000000000000909516949094176801000000000000000098821689021790925592519283529490920490931693509183917ffed767db50732333bba543b785430d53a3a836d71064a68ae91809e50eca7bb8910160405180910390a3505050565b5f54610100900460ff16158080156106c557505f54600160ff909116105b806106de5750303b1580156106de57505f5460ff166001145b61076a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840161028e565b5f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156107c6575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b73ffffffffffffffffffffffffffffffffffffffff8216610843576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f696e76616c6964206f776e657200000000000000000000000000000000000000604482015260640161028e565b61084b610e65565b61085482610def565b80156108b6575f80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6065545f9080610926576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f6e6f2073657175656e63657220636f6e66696775726564000000000000000000604482015260640161028e565b5f806109336001846110f8565b90505f5b8183116109d2575f600261094b848661113e565b6109559190611151565b90508667ffffffffffffffff166065828154811061097557610975611111565b5f9182526020909120015467ffffffffffffffff16116109b15780915082810361099f57506109d2565b6109aa81600161113e565b93506109cc565b805f036109be57506109d2565b6109c96001826110f8565b92505b50610937565b8567ffffffffffffffff16606582815481106109f0576109f0611111565b5f9182526020909120015467ffffffffffffffff161115610a6d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f6e6f2073657175656e6365722061742068656967687400000000000000000000604482015260640161028e565b60658181548110610a8057610a80611111565b5f9182526020909120015468010000000000000000900473ffffffffffffffffffffffffffffffffffffffff169695505050505050565b610abf610d6e565b60655415610b29576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f616c726561647920696e697469616c697a656400000000000000000000000000604482015260640161028e565b73ffffffffffffffffffffffffffffffffffffffff8216610ba6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f696e76616c696420616464726573730000000000000000000000000000000000604482015260640161028e565b60408051808201825267ffffffffffffffff83811680835273ffffffffffffffffffffffffffffffffffffffff8681166020808601828152606580546001810182555f91825297517f8ff97419363ffd7000167f130ef7168fbea05faf9251824ca5043f113cc6a7c79098018054925190951668010000000000000000027fffffffff00000000000000000000000000000000000000000000000000000000909216979096169690961795909517909155606680547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001683179055935190815290917ffed767db50732333bba543b785430d53a3a836d71064a68ae91809e50eca7bb8910160405180910390a35050565b610cbf610d6e565b73ffffffffffffffffffffffffffffffffffffffff8116610d62576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161028e565b610d6b81610def565b50565b60335473ffffffffffffffffffffffffffffffffffffffff1633146103d3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161028e565b6033805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b5f54610100900460ff16610efb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e67000000000000000000000000000000000000000000606482015260840161028e565b6103d35f54610100900460ff16610f94576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e67000000000000000000000000000000000000000000606482015260840161028e565b6103d333610def565b602080825282518282018190525f919060409081850190868401855b82811015610ffe578151805167ffffffffffffffff16855286015173ffffffffffffffffffffffffffffffffffffffff16868501529284019290850190600101610fb9565b5091979650505050505050565b5f6020828403121561101b575f80fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611045575f80fd5b919050565b803567ffffffffffffffff81168114611045575f80fd5b5f8060408385031215611072575f80fd5b61107b83611022565b91506110896020840161104a565b90509250929050565b5f602082840312156110a2575f80fd5b6110ab82611022565b9392505050565b5f602082840312156110c2575f80fd5b6110ab8261104a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b8181038181111561110b5761110b6110cb565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b8082018082111561110b5761110b6110cb565b5f82611184577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b50049056fea164736f6c6343000818000a", } // L1SequencerABI is the input ABI used to generate the binding from. @@ -202,6 +208,37 @@ func (_L1Sequencer *L1SequencerTransactorRaw) Transact(opts *bind.TransactOpts, return _L1Sequencer.Contract.contract.Transact(opts, method, params...) } +// ActiveHeight is a free data retrieval call binding the contract method 0x3ef5e8cc. +// +// Solidity: function activeHeight() view returns(uint64) +func (_L1Sequencer *L1SequencerCaller) ActiveHeight(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "activeHeight") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// ActiveHeight is a free data retrieval call binding the contract method 0x3ef5e8cc. +// +// Solidity: function activeHeight() view returns(uint64) +func (_L1Sequencer *L1SequencerSession) ActiveHeight() (uint64, error) { + return _L1Sequencer.Contract.ActiveHeight(&_L1Sequencer.CallOpts) +} + +// ActiveHeight is a free data retrieval call binding the contract method 0x3ef5e8cc. +// +// Solidity: function activeHeight() view returns(uint64) +func (_L1Sequencer *L1SequencerCallerSession) ActiveHeight() (uint64, error) { + return _L1Sequencer.Contract.ActiveHeight(&_L1Sequencer.CallOpts) +} + // GetSequencer is a free data retrieval call binding the contract method 0x4d96a90a. // // Solidity: function getSequencer() view returns(address) @@ -233,6 +270,99 @@ func (_L1Sequencer *L1SequencerCallerSession) GetSequencer() (common.Address, er return _L1Sequencer.Contract.GetSequencer(&_L1Sequencer.CallOpts) } +// GetSequencerAt is a free data retrieval call binding the contract method 0xf151ce9e. +// +// Solidity: function getSequencerAt(uint64 l2Height) view returns(address) +func (_L1Sequencer *L1SequencerCaller) GetSequencerAt(opts *bind.CallOpts, l2Height uint64) (common.Address, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "getSequencerAt", l2Height) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetSequencerAt is a free data retrieval call binding the contract method 0xf151ce9e. +// +// Solidity: function getSequencerAt(uint64 l2Height) view returns(address) +func (_L1Sequencer *L1SequencerSession) GetSequencerAt(l2Height uint64) (common.Address, error) { + return _L1Sequencer.Contract.GetSequencerAt(&_L1Sequencer.CallOpts, l2Height) +} + +// GetSequencerAt is a free data retrieval call binding the contract method 0xf151ce9e. +// +// Solidity: function getSequencerAt(uint64 l2Height) view returns(address) +func (_L1Sequencer *L1SequencerCallerSession) GetSequencerAt(l2Height uint64) (common.Address, error) { + return _L1Sequencer.Contract.GetSequencerAt(&_L1Sequencer.CallOpts, l2Height) +} + +// GetSequencerHistory is a free data retrieval call binding the contract method 0x6628aea1. +// +// Solidity: function getSequencerHistory() view returns((uint64,address)[]) +func (_L1Sequencer *L1SequencerCaller) GetSequencerHistory(opts *bind.CallOpts) ([]L1SequencerHistoryRecord, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "getSequencerHistory") + + if err != nil { + return *new([]L1SequencerHistoryRecord), err + } + + out0 := *abi.ConvertType(out[0], new([]L1SequencerHistoryRecord)).(*[]L1SequencerHistoryRecord) + + return out0, err + +} + +// GetSequencerHistory is a free data retrieval call binding the contract method 0x6628aea1. +// +// Solidity: function getSequencerHistory() view returns((uint64,address)[]) +func (_L1Sequencer *L1SequencerSession) GetSequencerHistory() ([]L1SequencerHistoryRecord, error) { + return _L1Sequencer.Contract.GetSequencerHistory(&_L1Sequencer.CallOpts) +} + +// GetSequencerHistory is a free data retrieval call binding the contract method 0x6628aea1. +// +// Solidity: function getSequencerHistory() view returns((uint64,address)[]) +func (_L1Sequencer *L1SequencerCallerSession) GetSequencerHistory() ([]L1SequencerHistoryRecord, error) { + return _L1Sequencer.Contract.GetSequencerHistory(&_L1Sequencer.CallOpts) +} + +// GetSequencerHistoryLength is a free data retrieval call binding the contract method 0x3d5767ce. +// +// Solidity: function getSequencerHistoryLength() view returns(uint256) +func (_L1Sequencer *L1SequencerCaller) GetSequencerHistoryLength(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Sequencer.contract.Call(opts, &out, "getSequencerHistoryLength") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetSequencerHistoryLength is a free data retrieval call binding the contract method 0x3d5767ce. +// +// Solidity: function getSequencerHistoryLength() view returns(uint256) +func (_L1Sequencer *L1SequencerSession) GetSequencerHistoryLength() (*big.Int, error) { + return _L1Sequencer.Contract.GetSequencerHistoryLength(&_L1Sequencer.CallOpts) +} + +// GetSequencerHistoryLength is a free data retrieval call binding the contract method 0x3d5767ce. +// +// Solidity: function getSequencerHistoryLength() view returns(uint256) +func (_L1Sequencer *L1SequencerCallerSession) GetSequencerHistoryLength() (*big.Int, error) { + return _L1Sequencer.Contract.GetSequencerHistoryLength(&_L1Sequencer.CallOpts) +} + // Owner is a free data retrieval call binding the contract method 0x8da5cb5b. // // Solidity: function owner() view returns(address) @@ -264,35 +394,49 @@ 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. +// SequencerHistory is a free data retrieval call binding the contract method 0x6d8ce3d2. // -// Solidity: function sequencer() view returns(address) -func (_L1Sequencer *L1SequencerCaller) Sequencer(opts *bind.CallOpts) (common.Address, error) { +// Solidity: function sequencerHistory(uint256 ) view returns(uint64 startL2Block, address sequencerAddr) +func (_L1Sequencer *L1SequencerCaller) SequencerHistory(opts *bind.CallOpts, arg0 *big.Int) (struct { + StartL2Block uint64 + SequencerAddr common.Address +}, error) { var out []interface{} - err := _L1Sequencer.contract.Call(opts, &out, "sequencer") + err := _L1Sequencer.contract.Call(opts, &out, "sequencerHistory", arg0) + outstruct := new(struct { + StartL2Block uint64 + SequencerAddr common.Address + }) if err != nil { - return *new(common.Address), err + return *outstruct, err } - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + outstruct.StartL2Block = *abi.ConvertType(out[0], new(uint64)).(*uint64) + outstruct.SequencerAddr = *abi.ConvertType(out[1], new(common.Address)).(*common.Address) - return out0, err + return *outstruct, err } -// Sequencer is a free data retrieval call binding the contract method 0x5c1bba38. +// SequencerHistory is a free data retrieval call binding the contract method 0x6d8ce3d2. // -// Solidity: function sequencer() view returns(address) -func (_L1Sequencer *L1SequencerSession) Sequencer() (common.Address, error) { - return _L1Sequencer.Contract.Sequencer(&_L1Sequencer.CallOpts) +// Solidity: function sequencerHistory(uint256 ) view returns(uint64 startL2Block, address sequencerAddr) +func (_L1Sequencer *L1SequencerSession) SequencerHistory(arg0 *big.Int) (struct { + StartL2Block uint64 + SequencerAddr common.Address +}, error) { + return _L1Sequencer.Contract.SequencerHistory(&_L1Sequencer.CallOpts, arg0) } -// Sequencer is a free data retrieval call binding the contract method 0x5c1bba38. +// SequencerHistory is a free data retrieval call binding the contract method 0x6d8ce3d2. // -// Solidity: function sequencer() view returns(address) -func (_L1Sequencer *L1SequencerCallerSession) Sequencer() (common.Address, error) { - return _L1Sequencer.Contract.Sequencer(&_L1Sequencer.CallOpts) +// Solidity: function sequencerHistory(uint256 ) view returns(uint64 startL2Block, address sequencerAddr) +func (_L1Sequencer *L1SequencerCallerSession) SequencerHistory(arg0 *big.Int) (struct { + StartL2Block uint64 + SequencerAddr common.Address +}, error) { + return _L1Sequencer.Contract.SequencerHistory(&_L1Sequencer.CallOpts, arg0) } // Initialize is a paid mutator transaction binding the contract method 0xc4d66de8. @@ -316,6 +460,27 @@ func (_L1Sequencer *L1SequencerTransactorSession) Initialize(_owner common.Addre return _L1Sequencer.Contract.Initialize(&_L1Sequencer.TransactOpts, _owner) } +// InitializeHistory is a paid mutator transaction binding the contract method 0xf198e27f. +// +// Solidity: function initializeHistory(address firstSequencer, uint64 upgradeL2Block) returns() +func (_L1Sequencer *L1SequencerTransactor) InitializeHistory(opts *bind.TransactOpts, firstSequencer common.Address, upgradeL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "initializeHistory", firstSequencer, upgradeL2Block) +} + +// InitializeHistory is a paid mutator transaction binding the contract method 0xf198e27f. +// +// Solidity: function initializeHistory(address firstSequencer, uint64 upgradeL2Block) returns() +func (_L1Sequencer *L1SequencerSession) InitializeHistory(firstSequencer common.Address, upgradeL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.Contract.InitializeHistory(&_L1Sequencer.TransactOpts, firstSequencer, upgradeL2Block) +} + +// InitializeHistory is a paid mutator transaction binding the contract method 0xf198e27f. +// +// Solidity: function initializeHistory(address firstSequencer, uint64 upgradeL2Block) returns() +func (_L1Sequencer *L1SequencerTransactorSession) InitializeHistory(firstSequencer common.Address, upgradeL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.Contract.InitializeHistory(&_L1Sequencer.TransactOpts, firstSequencer, upgradeL2Block) +} + // RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. // // Solidity: function renounceOwnership() returns() @@ -358,25 +523,25 @@ func (_L1Sequencer *L1SequencerTransactorSession) TransferOwnership(newOwner com return _L1Sequencer.Contract.TransferOwnership(&_L1Sequencer.TransactOpts, newOwner) } -// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// UpdateSequencer is a paid mutator transaction binding the contract method 0x761a90fd. // -// 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) +// Solidity: function updateSequencer(address newSequencer, uint64 startL2Block) returns() +func (_L1Sequencer *L1SequencerTransactor) UpdateSequencer(opts *bind.TransactOpts, newSequencer common.Address, startL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.contract.Transact(opts, "updateSequencer", newSequencer, startL2Block) } -// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// UpdateSequencer is a paid mutator transaction binding the contract method 0x761a90fd. // -// Solidity: function updateSequencer(address newSequencer) returns() -func (_L1Sequencer *L1SequencerSession) UpdateSequencer(newSequencer common.Address) (*types.Transaction, error) { - return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer) +// Solidity: function updateSequencer(address newSequencer, uint64 startL2Block) returns() +func (_L1Sequencer *L1SequencerSession) UpdateSequencer(newSequencer common.Address, startL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer, startL2Block) } -// UpdateSequencer is a paid mutator transaction binding the contract method 0x43ae20a3. +// UpdateSequencer is a paid mutator transaction binding the contract method 0x761a90fd. // -// Solidity: function updateSequencer(address newSequencer) returns() -func (_L1Sequencer *L1SequencerTransactorSession) UpdateSequencer(newSequencer common.Address) (*types.Transaction, error) { - return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer) +// Solidity: function updateSequencer(address newSequencer, uint64 startL2Block) returns() +func (_L1Sequencer *L1SequencerTransactorSession) UpdateSequencer(newSequencer common.Address, startL2Block uint64) (*types.Transaction, error) { + return _L1Sequencer.Contract.UpdateSequencer(&_L1Sequencer.TransactOpts, newSequencer, startL2Block) } // 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. @@ -737,12 +902,13 @@ func (it *L1SequencerSequencerUpdatedIterator) Close() error { type L1SequencerSequencerUpdated struct { OldSequencer common.Address NewSequencer common.Address + StartL2Block uint64 Raw types.Log // Blockchain specific contextual infos } -// FilterSequencerUpdated is a free log retrieval operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// FilterSequencerUpdated is a free log retrieval operation binding the contract event 0xfed767db50732333bba543b785430d53a3a836d71064a68ae91809e50eca7bb8. // -// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer, uint64 startL2Block) func (_L1Sequencer *L1SequencerFilterer) FilterSequencerUpdated(opts *bind.FilterOpts, oldSequencer []common.Address, newSequencer []common.Address) (*L1SequencerSequencerUpdatedIterator, error) { var oldSequencerRule []interface{} @@ -761,9 +927,9 @@ func (_L1Sequencer *L1SequencerFilterer) FilterSequencerUpdated(opts *bind.Filte return &L1SequencerSequencerUpdatedIterator{contract: _L1Sequencer.contract, event: "SequencerUpdated", logs: logs, sub: sub}, nil } -// WatchSequencerUpdated is a free log subscription operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// WatchSequencerUpdated is a free log subscription operation binding the contract event 0xfed767db50732333bba543b785430d53a3a836d71064a68ae91809e50eca7bb8. // -// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer, uint64 startL2Block) func (_L1Sequencer *L1SequencerFilterer) WatchSequencerUpdated(opts *bind.WatchOpts, sink chan<- *L1SequencerSequencerUpdated, oldSequencer []common.Address, newSequencer []common.Address) (event.Subscription, error) { var oldSequencerRule []interface{} @@ -807,9 +973,9 @@ func (_L1Sequencer *L1SequencerFilterer) WatchSequencerUpdated(opts *bind.WatchO }), nil } -// ParseSequencerUpdated is a log parse operation binding the contract event 0xcd58b762453bd126b48db83f2cecd464f5281dd7e5e6824b528c09d0482984d6. +// ParseSequencerUpdated is a log parse operation binding the contract event 0xfed767db50732333bba543b785430d53a3a836d71064a68ae91809e50eca7bb8. // -// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer) +// Solidity: event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer, uint64 startL2Block) func (_L1Sequencer *L1SequencerFilterer) ParseSequencerUpdated(log types.Log) (*L1SequencerSequencerUpdated, error) { event := new(L1SequencerSequencerUpdated) if err := _L1Sequencer.contract.UnpackLog(event, "SequencerUpdated", log); err != nil { diff --git a/contracts/contracts/l1/L1Sequencer.sol b/contracts/contracts/l1/L1Sequencer.sol index 3a46768b..d553cc89 100644 --- a/contracts/contracts/l1/L1Sequencer.sol +++ b/contracts/contracts/l1/L1Sequencer.sol @@ -4,55 +4,134 @@ pragma solidity =0.8.24; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /// @title L1Sequencer -/// @notice L1 contract for managing the sequencer address. -/// The sequencer address can be updated by the owner (multisig recommended). +/// @notice L1 contract for managing sequencer address with history tracking. +/// Supports querying which sequencer was active at any given L2 block height. contract L1Sequencer is OwnableUpgradeable { + // ============ Types ============ + + struct HistoryRecord { + uint64 startL2Block; + address sequencerAddr; + } + // ============ Storage ============ - /// @notice Current sequencer address - address public sequencer; + /// @notice Ordered array of sequencer records (by startL2Block ascending). + /// sequencerHistory[0] is the first sequencer after PBFT → single-sequencer upgrade. + HistoryRecord[] public sequencerHistory; + + /// @notice The L2 block height at which single-sequencer mode activates. + /// Set by initializeHistory(). Nodes read this to know when to switch consensus. + uint64 public activeHeight; // ============ Events ============ - /// @notice Emitted when sequencer is updated - event SequencerUpdated(address indexed oldSequencer, address indexed newSequencer); + event SequencerUpdated( + address indexed oldSequencer, + address indexed newSequencer, + uint64 startL2Block + ); // ============ Initializer ============ /// @notice Initialize the contract /// @param _owner Contract owner (multisig recommended) - /// @param _initialSequencer Initial sequencer address (can be address(0) to set later) - function initialize(address _owner, address _initialSequencer) external initializer { + function initialize(address _owner) external initializer { require(_owner != address(0), "invalid owner"); - __Ownable_init(); _transferOwnership(_owner); - - // Set initial sequencer if provided - if (_initialSequencer != address(0)) { - sequencer = _initialSequencer; - emit SequencerUpdated(address(0), _initialSequencer); - } } // ============ Admin Functions ============ - /// @notice Update sequencer address (takes effect immediately) - /// @param newSequencer New sequencer address - function updateSequencer(address newSequencer) external onlyOwner { - require(newSequencer != address(0), "invalid sequencer"); - require(newSequencer != sequencer, "same sequencer"); + /// @notice Initialize sequencer history (called once before the L2 upgrade). + /// @param firstSequencer The first sequencer address after the upgrade. + /// @param upgradeL2Block The L2 block height where single-sequencer mode activates. + function initializeHistory( + address firstSequencer, + uint64 upgradeL2Block + ) external onlyOwner { + require(sequencerHistory.length == 0, "already initialized"); + require(firstSequencer != address(0), "invalid address"); + + sequencerHistory.push(HistoryRecord({ + startL2Block: upgradeL2Block, + sequencerAddr: firstSequencer + })); + activeHeight = upgradeL2Block; + + emit SequencerUpdated(address(0), firstSequencer, upgradeL2Block); + } + + /// @notice Register a sequencer change at a future L2 block height. + /// The new sequencer is NOT active until startL2Block is reached. + /// @param newSequencer New sequencer address. + /// @param startL2Block L2 block height when the new sequencer takes over. + /// Must be strictly greater than the last record. + function updateSequencer( + address newSequencer, + uint64 startL2Block + ) external onlyOwner { + require(newSequencer != address(0), "invalid address"); + require(sequencerHistory.length > 0, "not initialized"); + require( + startL2Block > sequencerHistory[sequencerHistory.length - 1].startL2Block, + "startL2Block must be greater than last record" + ); - address oldSequencer = sequencer; - sequencer = newSequencer; + address oldSequencer = sequencerHistory[sequencerHistory.length - 1].sequencerAddr; - emit SequencerUpdated(oldSequencer, newSequencer); + sequencerHistory.push(HistoryRecord({ + startL2Block: startL2Block, + sequencerAddr: newSequencer + })); + + emit SequencerUpdated(oldSequencer, newSequencer, startL2Block); } // ============ View Functions ============ - /// @notice Get current sequencer address + /// @notice Get the sequencer that was active at a given L2 block height. + /// @dev Binary search: O(log n). + function getSequencerAt(uint64 l2Height) external view returns (address) { + uint256 len = sequencerHistory.length; + require(len > 0, "no sequencer configured"); + + uint256 low = 0; + uint256 high = len - 1; + uint256 result = 0; + + while (low <= high) { + uint256 mid = (low + high) / 2; + if (sequencerHistory[mid].startL2Block <= l2Height) { + result = mid; + if (mid == high) break; + low = mid + 1; + } else { + if (mid == 0) break; + high = mid - 1; + } + } + + require(sequencerHistory[result].startL2Block <= l2Height, "no sequencer at height"); + return sequencerHistory[result].sequencerAddr; + } + + /// @notice Get the latest registered sequencer address (backward compat). + /// @dev If the latest record's startL2Block hasn't been reached yet, + /// this address is scheduled but not yet active. function getSequencer() external view returns (address) { - return sequencer; + require(sequencerHistory.length > 0, "no sequencer configured"); + return sequencerHistory[sequencerHistory.length - 1].sequencerAddr; + } + + /// @notice Get the full sequencer history (for L2 node bulk sync at startup). + function getSequencerHistory() external view returns (HistoryRecord[] memory) { + return sequencerHistory; + } + + /// @notice Get the number of sequencer history records. + function getSequencerHistoryLength() external view returns (uint256) { + return sequencerHistory.length; } } diff --git a/contracts/contracts/test/L1Sequencer.t.sol b/contracts/contracts/test/L1Sequencer.t.sol new file mode 100644 index 00000000..24beecc9 --- /dev/null +++ b/contracts/contracts/test/L1Sequencer.t.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.24; + +import {L1SequencerBaseTest} from "./base/L1SequencerBase.t.sol"; +import {L1Sequencer} from "../l1/L1Sequencer.sol"; + +contract L1SequencerTest is L1SequencerBaseTest { + // ============ initialize ============ + + function test_initialize_setsOwner() public { + assertEq(l1Sequencer.owner(), owner); + } + + function test_initialize_revertOnReinit() public { + vm.expectRevert("Initializable: contract is already initialized"); + l1Sequencer.initialize(owner); + } + + function test_initialize_revertOnZeroOwner() public { + L1Sequencer impl = new L1Sequencer(); + vm.expectRevert("invalid owner"); + impl.initialize(address(0)); + } + + // ============ initializeHistory ============ + + function test_initializeHistory_success() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + assertEq(l1Sequencer.activeHeight(), UPGRADE_HEIGHT); + assertEq(l1Sequencer.getSequencerHistoryLength(), 1); + assertEq(l1Sequencer.getSequencer(), sequencerA); + assertEq(l1Sequencer.getSequencerAt(UPGRADE_HEIGHT), sequencerA); + } + + function test_initializeHistory_emitsEvent() public { + vm.expectEmit(true, true, false, true); + emit L1Sequencer.SequencerUpdated(address(0), sequencerA, UPGRADE_HEIGHT); + + vm.prank(owner); + l1Sequencer.initializeHistory(sequencerA, UPGRADE_HEIGHT); + } + + function test_initializeHistory_revertOnSecondCall() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("already initialized"); + vm.prank(owner); + l1Sequencer.initializeHistory(sequencerB, UPGRADE_HEIGHT + 100); + } + + function test_initializeHistory_revertOnZeroAddress() public { + vm.expectRevert("invalid address"); + vm.prank(owner); + l1Sequencer.initializeHistory(address(0), UPGRADE_HEIGHT); + } + + function test_initializeHistory_revertNonOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(nonOwner); + l1Sequencer.initializeHistory(sequencerA, UPGRADE_HEIGHT); + } + + // ============ updateSequencer ============ + + function test_updateSequencer_success() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT + 100); + + assertEq(l1Sequencer.getSequencerHistoryLength(), 2); + assertEq(l1Sequencer.getSequencer(), sequencerB); + } + + function test_updateSequencer_emitsEvent() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectEmit(true, true, false, true); + emit L1Sequencer.SequencerUpdated(sequencerA, sequencerB, UPGRADE_HEIGHT + 100); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT + 100); + } + + function test_updateSequencer_revertNotInitialized() public { + vm.expectRevert("not initialized"); + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, 200); + } + + function test_updateSequencer_revertZeroAddress() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("invalid address"); + vm.prank(owner); + l1Sequencer.updateSequencer(address(0), UPGRADE_HEIGHT + 100); + } + + function test_updateSequencer_revertStartBlockNotGreater() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("startL2Block must be greater than last record"); + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT); // equal, not greater + } + + function test_updateSequencer_revertStartBlockLessThanLast() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("startL2Block must be greater than last record"); + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT - 1); + } + + function test_updateSequencer_revertNonOwner() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(nonOwner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT + 100); + } + + // ============ getSequencerAt (binary search) ============ + + function test_getSequencerAt_singleRecord_exactHeight() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + assertEq(l1Sequencer.getSequencerAt(UPGRADE_HEIGHT), sequencerA); + } + + function test_getSequencerAt_singleRecord_aboveHeight() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + assertEq(l1Sequencer.getSequencerAt(UPGRADE_HEIGHT + 9999), sequencerA); + } + + function test_getSequencerAt_singleRecord_revertBelowHeight() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.expectRevert("no sequencer at height"); + l1Sequencer.getSequencerAt(UPGRADE_HEIGHT - 1); + } + + function test_getSequencerAt_multipleRecords() public { + _initHistory(sequencerA, 100); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, 200); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerC, 300); + + // Before first record + vm.expectRevert("no sequencer at height"); + l1Sequencer.getSequencerAt(99); + + // Exact boundaries + assertEq(l1Sequencer.getSequencerAt(100), sequencerA); + assertEq(l1Sequencer.getSequencerAt(200), sequencerB); + assertEq(l1Sequencer.getSequencerAt(300), sequencerC); + + // Between records + assertEq(l1Sequencer.getSequencerAt(150), sequencerA); + assertEq(l1Sequencer.getSequencerAt(199), sequencerA); + assertEq(l1Sequencer.getSequencerAt(250), sequencerB); + assertEq(l1Sequencer.getSequencerAt(299), sequencerB); + + // After last record + assertEq(l1Sequencer.getSequencerAt(1000), sequencerC); + } + + function test_getSequencerAt_twoRecords_boundary() public { + _initHistory(sequencerA, 100); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, 101); + + assertEq(l1Sequencer.getSequencerAt(100), sequencerA); + assertEq(l1Sequencer.getSequencerAt(101), sequencerB); + } + + function test_getSequencerAt_manyRecords_binarySearchStress() public { + _initHistory(sequencerA, 10); + + // Add 9 more records (10 total) + for (uint64 i = 1; i < 10; i++) { + address seq = address(uint160(0xA000 + i)); + vm.prank(owner); + l1Sequencer.updateSequencer(seq, 10 + i * 100); + } + + assertEq(l1Sequencer.getSequencerHistoryLength(), 10); + + // Query each boundary + assertEq(l1Sequencer.getSequencerAt(10), sequencerA); + assertEq(l1Sequencer.getSequencerAt(99), sequencerA); + assertEq(l1Sequencer.getSequencerAt(110), address(uint160(0xA001))); + assertEq(l1Sequencer.getSequencerAt(910), address(uint160(0xA009))); + assertEq(l1Sequencer.getSequencerAt(99999), address(uint160(0xA009))); + } + + function test_getSequencerAt_revertEmptyHistory() public { + vm.expectRevert("no sequencer configured"); + l1Sequencer.getSequencerAt(100); + } + + // ============ getSequencer ============ + + function test_getSequencer_revertEmpty() public { + vm.expectRevert("no sequencer configured"); + l1Sequencer.getSequencer(); + } + + function test_getSequencer_returnsLatest() public { + _initHistory(sequencerA, UPGRADE_HEIGHT); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, UPGRADE_HEIGHT + 100); + + assertEq(l1Sequencer.getSequencer(), sequencerB); + } + + // ============ getSequencerHistory ============ + + function test_getSequencerHistory_returnsAll() public { + _initHistory(sequencerA, 100); + + vm.prank(owner); + l1Sequencer.updateSequencer(sequencerB, 200); + + L1Sequencer.HistoryRecord[] memory history = l1Sequencer.getSequencerHistory(); + assertEq(history.length, 2); + assertEq(history[0].startL2Block, 100); + assertEq(history[0].sequencerAddr, sequencerA); + assertEq(history[1].startL2Block, 200); + assertEq(history[1].sequencerAddr, sequencerB); + } + + // ============ ownership ============ + + function test_transferOwnership() public { + vm.prank(owner); + l1Sequencer.transferOwnership(nonOwner); + assertEq(l1Sequencer.owner(), nonOwner); + + // New owner can now call admin functions + vm.prank(nonOwner); + l1Sequencer.initializeHistory(sequencerA, UPGRADE_HEIGHT); + assertEq(l1Sequencer.getSequencerHistoryLength(), 1); + } + + function test_renounceOwnership() public { + vm.prank(owner); + l1Sequencer.renounceOwnership(); + assertEq(l1Sequencer.owner(), address(0)); + + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(owner); + l1Sequencer.initializeHistory(sequencerA, UPGRADE_HEIGHT); + } +} diff --git a/contracts/contracts/test/base/L1SequencerBase.t.sol b/contracts/contracts/test/base/L1SequencerBase.t.sol new file mode 100644 index 00000000..3cdbb163 --- /dev/null +++ b/contracts/contracts/test/base/L1SequencerBase.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity =0.8.24; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import {L1Sequencer} from "../../l1/L1Sequencer.sol"; + +contract L1SequencerBaseTest is Test { + L1Sequencer public l1Sequencer; + ProxyAdmin public proxyAdmin; + + address public owner = address(0x1234); + address public nonOwner = address(0x5678); + address public sequencerA = address(0xA001); + address public sequencerB = address(0xA002); + address public sequencerC = address(0xA003); + + uint64 public constant UPGRADE_HEIGHT = 100; + + function setUp() public virtual { + vm.startPrank(owner); + + proxyAdmin = new ProxyAdmin(); + L1Sequencer impl = new L1Sequencer(); + + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(impl), + address(proxyAdmin), + abi.encodeWithSelector(L1Sequencer.initialize.selector, owner) + ); + + l1Sequencer = L1Sequencer(address(proxy)); + vm.stopPrank(); + } + + function _initHistory(address seq, uint64 upgradeHeight) internal { + vm.prank(owner); + l1Sequencer.initializeHistory(seq, upgradeHeight); + } +} diff --git a/contracts/deploy/022-SequencerInit.ts b/contracts/deploy/022-SequencerInit.ts index e8de2511..d0ff3329 100644 --- a/contracts/deploy/022-SequencerInit.ts +++ b/contracts/deploy/022-SequencerInit.ts @@ -34,20 +34,12 @@ export const SequencerInit = async ( // 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 + // Upgrade and initialize the proxy with owner only. + // Sequencer history is initialized separately via initializeHistory(). await IL1SequencerProxy.upgradeToAndCall( L1SequencerImplAddress, - L1SequencerFactory.interface.encodeFunctionData('initialize', [owner, initialSequencer]) + L1SequencerFactory.interface.encodeFunctionData('initialize', [owner]) ) await awaitCondition( @@ -72,16 +64,7 @@ export const SequencerInit = async ( 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)') - } + console.log('L1SequencerProxy upgrade success') } return '' } diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index bc39e33c..5d06bb92 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -16,7 +16,6 @@ import ( 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" @@ -76,11 +75,6 @@ func L2NodeMain(ctx *cli.Context) error { 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 } @@ -211,6 +205,9 @@ func L2NodeMain(ctx *cli.Context) error { if tracker != nil { tracker.Stop() } + if verifier != nil { + verifier.Stop() + } return nil } @@ -243,18 +240,17 @@ func initL1SequencerComponents( } logger.Info("L1 Tracker started", "lagThreshold", lagThreshold) - // Initialize Sequencer Verifier (optional) + // Initialize Sequencer Verifier 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") + return nil, nil, nil, fmt.Errorf("L1 Sequencer contract address is required, check l1.sequencerContract configuration") } // Initialize Signer (optional) @@ -263,12 +259,10 @@ func initL1SequencerComponents( 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) + signer, err = l1sequencer.NewLocalSigner(privKey, logger) if err != nil { - tracker.Stop() return nil, nil, nil, err } logger.Info("Sequencer signer initialized", "address", signer.Address().Hex()) diff --git a/node/flags/flags.go b/node/flags/flags.go index 6d37943f..e3149f6a 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -276,14 +276,6 @@ 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", @@ -407,9 +399,6 @@ var Flags = []cli.Flag{ L1SyncLagThreshold, SequencerPrivateKey, - // consensus - ConsensusSwitchHeight, - // batch rules UpgradeBatchTime, MainnetFlag, diff --git a/node/l1sequencer/signer.go b/node/l1sequencer/signer.go index f03901ae..4ad85130 100644 --- a/node/l1sequencer/signer.go +++ b/node/l1sequencer/signer.go @@ -1,7 +1,6 @@ package l1sequencer import ( - "context" "crypto/ecdsa" "fmt" @@ -19,32 +18,25 @@ type Signer interface { // 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 + privKey *ecdsa.PrivateKey + address common.Address + 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) { +func NewLocalSigner(privKey *ecdsa.PrivateKey, 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"), + privKey: privKey, + address: crypto.PubkeyToAddress(privKey.PublicKey), + logger: logger.With("module", "signer"), }, nil } @@ -62,10 +54,3 @@ 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/verifier.go b/node/l1sequencer/verifier.go index 1cbf8517..31271422 100644 --- a/node/l1sequencer/verifier.go +++ b/node/l1sequencer/verifier.go @@ -3,95 +3,206 @@ package l1sequencer import ( "context" "fmt" + "math" + "math/big" + "sort" "sync" "time" "github.com/morph-l2/go-ethereum/accounts/abi/bind" "github.com/morph-l2/go-ethereum/common" + "github.com/morph-l2/go-ethereum/rpc" tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/upgrade" "morph-l2/bindings/bindings" ) -const ( - // CacheTTL is the time-to-live for the sequencer verifier cache - //CacheTTL = 30 * time.Minute - CacheTTL = 10 * time.Second -) +const refreshInterval = 5 * time.Minute + +// sequencerCursor caches the current sequencer interval for O(1) lookup. +type sequencerCursor struct { + from uint64 + to uint64 // exclusive; math.MaxUint64 = no upper bound + addr common.Address + valid bool +} -// SequencerVerifier verifies L1 sequencer status with caching. -// It provides IsSequencer() for checking if an address is the current sequencer. +// SequencerVerifier verifies L1 sequencer status. +// Implements tendermint SequencerVerifier interface. +// +// History is loaded from L1 at construction and refreshed every 5 minutes. +// All L1 reads use the finalized block tag to avoid ingesting reorged data. type SequencerVerifier struct { - mutex sync.Mutex - sequencer common.Address - cacheExpiry time.Time + mu sync.Mutex + history []bindings.L1SequencerHistoryRecord + cursor sequencerCursor caller *bindings.L1SequencerCaller logger tmlog.Logger + cancel context.CancelFunc } -// NewSequencerVerifier creates a new SequencerVerifier +// NewSequencerVerifier creates a new SequencerVerifier, loads the full sequencer +// history from L1 (finalized), and starts a background refresh goroutine. +// Call Stop to terminate the background loop. func NewSequencerVerifier(caller *bindings.L1SequencerCaller, logger tmlog.Logger) *SequencerVerifier { - return &SequencerVerifier{ + ctx, cancel := context.WithCancel(context.Background()) + v := &SequencerVerifier{ caller: caller, logger: logger.With("module", "l1sequencer_verifier"), + cancel: cancel, } + if err := v.syncHistory(); err != nil { + v.logger.Error("Failed to load sequencer history from L1", "err", err) + } + go v.refreshLoop(ctx) + return v +} + +// Stop terminates the background refresh loop. +func (c *SequencerVerifier) Stop() { + c.cancel() } -// 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}) +// syncHistory fetches the full sequencer history from L1 (finalized tag) and +// replaces the local cache if anything changed. +func (c *SequencerVerifier) syncHistory() error { + raw, err := c.caller.GetSequencerHistory(&bind.CallOpts{ + BlockNumber: big.NewInt(int64(rpc.FinalizedBlockNumber)), + }) if err != nil { - return fmt.Errorf("failed to get sequencer from L1: %w", err) + return fmt.Errorf("GetSequencerHistory: %w", err) } - if c.sequencer != newSeq { - c.logger.Info("Sequencer address updated", - "old", c.sequencer.Hex(), - "new", newSeq.Hex()) + c.mu.Lock() + defer c.mu.Unlock() + + if len(raw) == len(c.history) { + return nil // no change + } + + prev := len(c.history) + c.history = raw + // Only invalidate cursor if it was pointing at the last record (to == MaxUint64), + // because new records change that interval's upper bound. + // Existing records never change, so earlier cursor intervals remain valid. + if c.cursor.valid && c.cursor.to == math.MaxUint64 { + c.cursor.valid = false } - c.sequencer = newSeq - c.cacheExpiry = time.Now().Add(CacheTTL) + // Log new records + for i := prev; i < len(c.history); i++ { + c.logger.Info("Sequencer record", + "startL2Block", c.history[i].StartL2Block, + "address", c.history[i].SequencerAddr.Hex()) + } + // Set upgrade height from L1 contract on first successful load + if prev == 0 && len(c.history) > 0 { + height := int64(c.history[0].StartL2Block) + upgrade.SetUpgradeBlockHeight(height) + c.logger.Info("Upgrade height set from L1 contract", "height", height) + } + + c.logger.Info("Sequencer history synced", "total", len(c.history), "new", len(c.history)-prev) 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() +// refreshLoop polls L1 until ctx is cancelled. +// Uses exponential backoff (10s -> 20s -> ... -> 5min) while history is empty, +// then switches to the normal 5-minute interval once loaded. +func (c *SequencerVerifier) refreshLoop(ctx context.Context) { + const minRetry = 10 * time.Second + + interval := refreshInterval + c.mu.Lock() + empty := len(c.history) == 0 + c.mu.Unlock() + if empty { + interval = minRetry + } - // Cache expired, refresh - if time.Now().After(c.cacheExpiry) { - if err := c.flushCache(ctx); err != nil { - return false, err + timer := time.NewTimer(interval) + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-timer.C: + if err := c.syncHistory(); err != nil { + c.logger.Error("Failed to refresh sequencer history", "err", err) + } + + c.mu.Lock() + empty = len(c.history) == 0 + c.mu.Unlock() + + if empty { + // Exponential backoff, capped at refreshInterval + interval = interval * 2 + if interval > refreshInterval { + interval = refreshInterval + } + } else { + interval = refreshInterval + } + timer.Reset(interval) } } +} + +// SequencerAtHeight returns the sequencer address at the given L2 height. +func (c *SequencerVerifier) SequencerAtHeight(l2Height uint64) (common.Address, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + if len(c.history) == 0 { + return common.Address{}, false + } - // Cache hit - if c.sequencer == addr { - return true, nil + if c.cursor.valid && l2Height >= c.cursor.from && l2Height < c.cursor.to { + return c.cursor.addr, true } - // Cache miss - maybe sequencer just updated, force refresh once - if err := c.flushCache(ctx); err != nil { - return false, err + idx := sort.Search(len(c.history), func(i int) bool { + return c.history[i].StartL2Block > l2Height + }) - 1 + if idx < 0 { + return common.Address{}, false } - return c.sequencer == addr, nil + c.cursor.from = c.history[idx].StartL2Block + if idx+1 < len(c.history) { + c.cursor.to = c.history[idx+1].StartL2Block + } else { + c.cursor.to = math.MaxUint64 + } + c.cursor.addr = c.history[idx].SequencerAddr + c.cursor.valid = true + return c.cursor.addr, true } -// 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() +// ============================================================================ +// Interface implementation +// ============================================================================ - if time.Now().After(c.cacheExpiry) { - if err := c.flushCache(ctx); err != nil { - return common.Address{}, err - } +// IsSequencerAt checks if addr was the sequencer at the given L2 height. +func (c *SequencerVerifier) IsSequencerAt(addr common.Address, l2Height uint64) (bool, error) { + histAddr, found := c.SequencerAtHeight(l2Height) + if !found { + return false, fmt.Errorf("no sequencer record for height %d", l2Height) } + return addr == histAddr, nil +} - return c.sequencer, nil +// VerificationStartHeight returns history[0].StartL2Block (= contract activeHeight). +// Returns math.MaxUint64 if history is empty. +func (c *SequencerVerifier) VerificationStartHeight() uint64 { + c.mu.Lock() + defer c.mu.Unlock() + if len(c.history) == 0 { + return math.MaxUint64 + } + return c.history[0].StartL2Block } diff --git a/ops/docker-sequencer-test/docker-compose.override.yml b/ops/docker-sequencer-test/docker-compose.override.yml index 9cc69cae..b40946d0 100644 --- a/ops/docker-sequencer-test/docker-compose.override.yml +++ b/ops/docker-sequencer-test/docker-compose.override.yml @@ -27,16 +27,15 @@ services: - 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_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} + - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} node-2: @@ -44,7 +43,7 @@ services: 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} + - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} node-3: @@ -52,7 +51,7 @@ services: 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} + - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} sentry-geth-0: @@ -61,5 +60,46 @@ services: sentry-node-0: image: morph-node-test:latest environment: - - MORPH_NODE_CONSENSUS_SWITCH_HEIGHT=${CONSENSUS_SWITCH_HEIGHT:-10} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + + # ========== Malicious Node (P2P security test) ========== + # Uses morph-node-malicious:latest built from test/p2p-security branch. + # No SEQUENCER_PRIVATE_KEY -> fullnode mode. Attack via MALICIOUS_MODE env. + malicious-geth-0: + image: morph-geth-test:latest + volumes: + - malicious_geth_data:/db + - ../l2-genesis/.devnet/genesis-l2.json:/genesis.json + - ./jwt-secret.txt:/jwt-secret.txt + - ./static-nodes.json:/db/geth/static-nodes.json + entrypoint: + - "/bin/sh" + - "/entrypoint.sh" + ports: + - "9045:8545" + + malicious-node-0: + image: morph-node-malicious:latest + depends_on: + malicious-geth-0: + condition: service_started + environment: + - MALICIOUS_MODE=${MALICIOUS_MODE:-} + - EMPTY_BLOCK_DELAY=true + - MORPH_NODE_L2_ETH_RPC=http://malicious-geth-0:8545 + - MORPH_NODE_L2_ENGINE_RPC=http://malicious-geth-0:8551 + - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} + - MORPH_NODE_L1_ETH_RPC=${L1_ETH_RPC} + - MORPH_NODE_L1_SEQUENCER_CONTRACT=${L1_SEQUENCER_CONTRACT} + - MORPH_NODE_ROLLUP_ADDRESS=${MORPH_ROLLUP:-0x6900000000000000000000000000000000000010} + - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} + - MORPH_NODE_L1_CONFIRMATIONS=0 + volumes: + - .devnet/malicious-node-0:${NODE_DATA_DIR} + - ${PWD}/jwt-secret.txt:${JWT_SECRET_PATH} + command: > + morphnode + --home $NODE_DATA_DIR diff --git a/ops/docker-sequencer-test/run-test.sh b/ops/docker-sequencer-test/run-test.sh index 81361fef..4132791d 100755 --- a/ops/docker-sequencer-test/run-test.sh +++ b/ops/docker-sequencer-test/run-test.sh @@ -56,7 +56,7 @@ get_block_number() { 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" + echo "$result" | grep -o '"result":"[^"]*"' | cut -d'"' -f4 | xargs printf "%d" 2>/dev/null || true } wait_for_block() { @@ -77,15 +77,6 @@ wait_for_block() { # ========== 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() { @@ -213,6 +204,26 @@ for i in range(4): '--private-key', deploy_config['l2StakingPks'][i] ]) +# Initialize L1Sequencer history for V2 mode +# Register the first sequencer (node-0's staking address) at upgrade height +l1_sequencer_addr = addresses.get('Proxy__L1Sequencer', '') +if l1_sequencer_addr: + upgrade_height = os.environ.get('UPGRADE_HEIGHT', '10') + sequencer_addr = deploy_config['l2StakingAddresses'][0] # node-0's address + deployer_pk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + log.info(f'Initializing L1Sequencer history: sequencer={sequencer_addr}, startL2Block={upgrade_height}') + try: + run_command([ + 'cast', 'send', l1_sequencer_addr, + 'initializeHistory(address,uint64)', + sequencer_addr, str(upgrade_height), + '--rpc-url', 'http://127.0.0.1:9545', + '--private-key', deployer_pk + ]) + log.info('L1Sequencer history initialized successfully') + except Exception as e: + log.info(f'L1Sequencer initializeHistory failed (may already be initialized): {e}') + # Update .env file log.info('Updating .env file...') env_file = pjoin(ops_dir, '.env') @@ -259,40 +270,80 @@ remove_override() { rm -f "$DOCKER_DIR/docker-compose.override.yml" } +# Wait for L1 finalized block to reach at least the given height. +# This ensures contract data (e.g., initializeHistory) is visible via +# the finalized block tag when L2 nodes start their verifier sync. +wait_for_l1_finalized() { + local min_block=${1:-1} + local l1_rpc="${2:-http://127.0.0.1:9545}" + local max_wait=120 + local waited=0 + + log_info "Waiting for L1 finalized block >= $min_block..." + while [ $waited -lt $max_wait ]; do + local fin + fin=$(curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["finalized",false],"id":1}' \ + "$l1_rpc" 2>/dev/null | grep -o '"number":"0x[^"]*"' | head -1 | cut -d'"' -f4) + if [ -n "$fin" ]; then + local fin_dec + fin_dec=$(printf "%d" "$fin" 2>/dev/null || echo 0) + if [ "$fin_dec" -ge "$min_block" ]; then + log_success "L1 finalized block: $fin_dec (>= $min_block)" + return 0 + fi + echo -ne "\r L1 finalized: $fin_dec / $min_block" + fi + sleep 3 + waited=$((waited + 3)) + done + log_warn "Timeout waiting for L1 finalized >= $min_block (continuing anyway)" +} + # 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" - + + # Wait for L1 to finalize past the contract deployment block. + # The verifier reads history via finalized tag; if L1 hasn't finalized + # the initializeHistory tx yet, the initial sync will miss it. + local l1_latest + l1_latest=$(curl -s -X POST -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + http://127.0.0.1:9545 2>/dev/null | grep -o '"result":"0x[^"]*"' | cut -d'"' -f4) + l1_latest=$(printf "%d" "$l1_latest" 2>/dev/null || echo 1) + wait_for_l1_finalized "$l1_latest" + # 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 + $COMPOSE_CMD up -d morph-geth-0 morph-geth-1 morph-geth-2 morph-geth-3 sentry-geth-0 sleep 5 # Start L2 tendermint nodes log_info "Starting L2 tendermint nodes..." - $COMPOSE_CMD up -d node-0 node-1 node-2 node-3 - + $COMPOSE_CMD up -d node-0 node-1 node-2 node-3 sentry-node-0 + wait_for_rpc "$L2_RPC" log_success "L2 is running with test images!" } @@ -436,10 +487,7 @@ run_full_test() { 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 build_test_images # Setup devnet (L1 + contracts + L2 genesis) @@ -477,6 +525,335 @@ show_logs() { $COMPOSE_CMD_NO_OVERRIDE logs -f "$@" } +# ========== Malicious Image Build ========== + +build_malicious_image() { + log_info "Building malicious node image from test/p2p-security branch..." + cd "$BITGET_ROOT" + + # Save current tendermint branch state + cd tendermint + local original_branch + original_branch=$(git branch --show-current) + git stash 2>/dev/null || true + + # Switch to malicious branch + git checkout test/p2p-security + cd "$BITGET_ROOT" + + # Build using same Dockerfile, different tag + docker build -t morph-node-malicious:latest \ + -f morph/ops/docker-sequencer-test/Dockerfile.l2-node-test . + + # Switch back + cd tendermint + git checkout "$original_branch" + git stash pop 2>/dev/null || true + cd "$BITGET_ROOT" + + log_success "Malicious image built!" +} + +# ========== P2P Security Test ========== + +L2_RPC_SENTRY="http://127.0.0.1:8945" + +# Swap sentry-node-0 to use malicious image, keeping its data. +# This is the practical approach: a malicious node must be synced first (fresh +# nodes from height 0 can't connect after PBFT->V2 upgrade). By swapping the +# sentry's image, the malicious node starts already synced and connected. +start_malicious_sentry() { + local mode="${1:-all}" + log_info "Swapping sentry-node-0 to malicious image (MALICIOUS_MODE=$mode)..." + cd "$DOCKER_DIR" + + # Stop sentry + $COMPOSE_CMD stop sentry-node-0 2>/dev/null || true + $COMPOSE_CMD rm -f sentry-node-0 2>/dev/null || true + + # Restart with malicious image via env override + export MALICIOUS_MODE="$mode" + SENTRY_IMAGE=morph-node-malicious:latest \ + docker compose -f docker-compose-4nodes.yml -f docker-compose.override.yml \ + run -d --name sentry-node-0-malicious \ + -e MALICIOUS_MODE="$mode" \ + --entrypoint "" \ + morph-node-malicious:latest \ + morphnode --home /data 2>/dev/null || true + + # Simpler: just modify the override to use malicious image for sentry + # and restart + $COMPOSE_CMD up -d sentry-node-0 +} + +# Actually, the simplest approach: temporarily edit docker-compose to use +# the malicious image for sentry-node-0, then restart it. +swap_sentry_to_malicious() { + local mode="${1:-all}" + log_info "Swapping sentry to malicious image (mode=$mode)..." + cd "$DOCKER_DIR" + + # Stop sentry + $COMPOSE_CMD stop sentry-node-0 2>/dev/null || true + $COMPOSE_CMD rm -f sentry-node-0 2>/dev/null || true + + # Create a temp override that changes sentry image to malicious. + # IMPORTANT: docker compose replaces the entire environment list, not merge. + # Must include ALL required env vars here. + cat > docker-compose.malicious-override.yml </dev/null || true + $COMPOSE_CMD rm -f sentry-node-0 2>/dev/null || true + rm -f docker-compose.malicious-override.yml + + # Restart with normal image + $COMPOSE_CMD up -d sentry-node-0 +} + +test_p2p_security() { + log_info "==========================================" + log_info " P2P Anti-Malicious Security Tests" + log_info "==========================================" + + cd "$DOCKER_DIR" + + # ========================================== + # Phase 0: Precondition checks + # ========================================== + local height + height=$(get_block_number "$L2_RPC") + + # Check 1: chain must be past upgrade height (read from L1 contract via verifier) + if [ "$height" -le "$UPGRADE_HEIGHT" ]; then + log_error "Chain height ($height) <= UPGRADE_HEIGHT ($UPGRADE_HEIGHT). V2 not active." + return 1 + fi + + # Check 2: node-0 must be in V2 mode with signer + local node0_v2 + node0_v2=$($COMPOSE_CMD logs node-0 2>/dev/null | grep -c "StateV2 initialized.*hasSigner=true" || true) + if [ "$node0_v2" -lt 1 ]; then + log_error "node-0 not in V2 mode with signer. Check SEQUENCER_PRIVATE_KEY and L1 initializeHistory." + return 1 + fi + + # Check 3: sentry must be in V2 path (not PBFT consensus reactor) + local sentry_v2 + sentry_v2=$($COMPOSE_CMD logs sentry-node-0 2>/dev/null | grep -c "Starting block apply routine" || true) + if [ "$sentry_v2" -lt 1 ]; then + log_error "sentry-node-0 not in V2 path. Check L1 contract initializeHistory." + return 1 + fi + + log_info "Preconditions OK: height=$height, upgradeHeight=$UPGRADE_HEIGHT, V2 active" + + local pass=0 + local fail=0 + local skip=0 + + # Strategy: swap sentry-node-0's image to the malicious one. + # The sentry is already synced, so the malicious node starts with full + # P2P connectivity and can immediately execute attacks. + # Other nodes (node-0~3) are the "victims" that should reject forged blocks. + + # ========================================== + # Phase 1: Active attacks (T-01 ~ T-05) + # ========================================== + log_info "---------- Phase 1: Active attacks ----------" + + # Record log baseline for all victim nodes + local log_baseline="/tmp/p2p_log_baseline_$$.txt" + $COMPOSE_CMD logs node-0 node-1 node-2 node-3 2>/dev/null | wc -l > "$log_baseline" + + swap_sentry_to_malicious "all" + log_info "Waiting for malicious routine (~40s)..." + sleep 40 + + # Dump logs + local mal_log="/tmp/mal_p2p_$$.log" + docker compose \ + -f docker-compose-4nodes.yml \ + -f docker-compose.override.yml \ + -f docker-compose.malicious-override.yml \ + logs sentry-node-0 2>/dev/null > "$mal_log" + + local victim_log="/tmp/victim_p2p_$$.log" + $COMPOSE_CMD logs node-0 node-1 node-2 node-3 2>/dev/null > "$victim_log" + + restore_sentry_to_normal + + # Check malicious node executed attacks + local mal_attacks + mal_attacks=$(grep -c "\[MALICIOUS\]" "$mal_log" 2>/dev/null || true) + log_info "Malicious node executed $mal_attacks attack log entries" + + # T-01/02/03: Signature attacks (check victim nodes) + local sig_reject + sig_reject=$(grep -c "Block signature verification failed" "$victim_log" 2>/dev/null || true) + if [ "$sig_reject" -ge 3 ]; then + log_success "T-01/02/03 Signature attacks: PASSED ($sig_reject blocks rejected)" + pass=$((pass + 1)) + else + log_error "T-01/02/03 Signature attacks: FAILED ($sig_reject rejections, expected >= 3)" + fail=$((fail + 1)) + fi + + # T-04: Unsolicited sync (check victim nodes) + local unsolicited + unsolicited=$(grep -c "Unsolicited sync response" "$victim_log" 2>/dev/null || true) + if [ "$unsolicited" -ge 1 ]; then + log_success "T-04 Unsolicited sync: PASSED ($unsolicited dropped)" + pass=$((pass + 1)) + else + # Unsolicited sync targets random peers, may not hit victim nodes + log_warn "T-04 Unsolicited sync: SKIPPED (no rejection logs on victim nodes)" + skip=$((skip + 1)) + fi + + # T-05: Duplicate flood (check victim nodes) + local dedup + dedup=$(grep -c "broadcast dedup" "$victim_log" 2>/dev/null || true) + if [ "$dedup" -ge 1 ]; then + log_success "T-05 Duplicate flood: PASSED ($dedup deduped)" + pass=$((pass + 1)) + else + log_warn "T-05 Duplicate flood: SKIPPED (debug log not visible)" + skip=$((skip + 1)) + fi + + rm -f "$mal_log" "$victim_log" "$log_baseline" + + # ========================================== + # Phase 2: BlockSync forge (T-06) - V1 main vulnerability + # ========================================== + log_info "---------- Phase 2: BlockSync forge (T-06) ----------" + log_info "Testing blocksync/reactor.go:respondToPeerV2 path (BlocksyncChannel 0x40)" + + # Step 1: Swap sentry to malicious image (blocksync-forge mode) + # The malicious sentry will respond to BlockSync requests with forged blocks + swap_sentry_to_malicious "blocksync-forge" + sleep 5 + + # Step 2: Stop node-3 to create a sync gap + log_info "Stopping node-3 to create BlockSync gap..." + $COMPOSE_CMD stop node-3 2>/dev/null || true + sleep 20 # Let chain advance while node-3 is down + + # Step 3: Restart node-3 — it will BlockSync from peers (including malicious sentry) + log_info "Restarting node-3 (will BlockSync from peers including malicious sentry)..." + $COMPOSE_CMD start node-3 + + # Step 4: Wait for node-3 to catch up + local target_height + target_height=$(get_block_number "$L2_RPC") + log_info "Waiting for node-3 to sync to ~$target_height..." + local max_wait=120 + local waited=0 + while [ $waited -lt $max_wait ]; do + local n3_height + n3_height=$(get_block_number "http://127.0.0.1:8845") + if [ "$n3_height" -ge "$((target_height - 3))" ]; then + log_info "node-3 synced to $n3_height" + break + fi + sleep 5 + waited=$((waited + 5)) + done + + # Step 5: Dump logs (separate files for isolation) + local mal_bs_log="/tmp/mal_blocksync_$$.log" + docker compose \ + -f docker-compose-4nodes.yml \ + -f docker-compose.override.yml \ + -f docker-compose.malicious-override.yml \ + logs sentry-node-0 2>/dev/null > "$mal_bs_log" + + local node3_log="/tmp/node3_blocksync_$$.log" + $COMPOSE_CMD logs node-3 2>/dev/null > "$node3_log" + + restore_sentry_to_normal + + # Step 6: Verify + local bs_forged + bs_forged=$(grep -c "\[MALICIOUS\] Sent forged blocksync response" "$mal_bs_log" 2>/dev/null || true) + local bs_rejected + bs_rejected=$(grep -c "Block signature verification failed" "$node3_log" 2>/dev/null || true) + local n3_final + n3_final=$(get_block_number "http://127.0.0.1:8845") + + rm -f "$mal_bs_log" "$node3_log" + + if [ "$bs_forged" -ge 1 ] && [ "$bs_rejected" -ge 1 ]; then + log_success "T-06 BlockSync forge: PASSED (sent $bs_forged forged, rejected $bs_rejected, node-3 at $n3_final)" + pass=$((pass + 1)) + elif [ "$bs_forged" -ge 1 ]; then + log_warn "T-06 BlockSync forge: PARTIAL (sent $bs_forged forged, but node-3 may not have queried malicious peer)" + skip=$((skip + 1)) + else + log_warn "T-06 BlockSync forge: SKIPPED (malicious sentry not queried via BlockSync)" + skip=$((skip + 1)) + fi + + # ========================================== + # Phase 3: Network resilience (T-07) + # ========================================== + log_info "---------- Phase 3: Network resilience ----------" + + local h1 + h1=$(get_block_number "$L2_RPC") + sleep 30 + local h2 + h2=$(get_block_number "$L2_RPC") + if [ "$h2" -gt "$h1" ]; then + log_success "T-07 Network resilience: PASSED ($h1 -> $h2)" + pass=$((pass + 1)) + else + log_error "T-07 Network resilience: FAILED (height stuck at $h1)" + fail=$((fail + 1)) + fi + + # ========================================== + # Results + # ========================================== + log_info "==========================================" + if [ "$fail" -eq 0 ]; then + log_success " P2P Security: $pass PASSED, $skip SKIPPED, $fail FAILED" + log_success "==========================================" + else + log_error " P2P Security: $pass PASSED, $skip SKIPPED, $fail FAILED" + log_error "==========================================" + return 1 + fi +} + # ========== Command Parsing ========== case "${1:-}" in @@ -513,35 +890,41 @@ case "${1:-}" in status) show_status ;; - upgrade-height) - set_upgrade_height "${2:-50}" + build-malicious) + build_malicious_image + ;; + p2p-test) + test_p2p_security ;; *) echo "Sequencer Upgrade Test Runner" echo "" - echo "Usage: $0 {build|setup|start|stop|clean|logs|test|tx|status|upgrade-height}" + echo "Usage: $0 {build|setup|start|stop|clean|logs|test|tx|status|build-malicious|p2p-test}" 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 " build - Build test Docker images (morph-geth-test, morph-node-test)" + echo " build-malicious - Build malicious node image from test/p2p-security branch" + 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 " p2p-test - Run P2P anti-malicious security tests" + echo " tx - Start transaction generator" + echo " status - Show current block numbers" echo "" - echo "Environment Variables:" - echo " UPGRADE_HEIGHT - Block height for consensus switch (default: 10)" - echo " TX_INTERVAL - Seconds between txs (default: 5)" + echo "Environment Variables:" + echo " UPGRADE_HEIGHT - Block height for consensus switch (default: 10)" + echo " TX_INTERVAL - Seconds between txs (default: 5)" + echo " MALICIOUS_MODE - Attack mode for p2p-test (default: all)" 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 " 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 " 5. p2p-test - Run P2P security tests (requires build-malicious)" echo "" echo "Quick Start:" echo " UPGRADE_HEIGHT=10 $0 test" diff --git a/ops/docker/docker-compose-4nodes.yml b/ops/docker/docker-compose-4nodes.yml index 255d2a13..766bf692 100644 --- a/ops/docker/docker-compose-4nodes.yml +++ b/ops/docker/docker-compose-4nodes.yml @@ -19,6 +19,7 @@ volumes: layer1-el-data: layer1-cl-data: layer1-vc-data: + malicious_geth_data: services: # ========== Layer1 Ethereum Node ========== @@ -264,7 +265,7 @@ services: - MORPH_NODE_L2_ENGINE_RPC=http://morph-geth-1:8551 - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} - MORPH_NODE_L1_ETH_RPC=${L1_ETH_RPC} - - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} + # - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} - MORPH_NODE_L1_CONFIRMATIONS=0 - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} - MORPH_NODE_UPGRADE_BATCH_TIME=${BATCH_UPGRADE_TIME} @@ -376,9 +377,9 @@ services: - MORPH_NODE_L2_ENGINE_RPC=http://sentry-geth-0:8551 - MORPH_NODE_L2_ENGINE_AUTH=${JWT_SECRET_PATH} - MORPH_NODE_L1_ETH_RPC=${L1_ETH_RPC} - - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} + # - MORPH_NODE_SYNC_DEPOSIT_CONTRACT_ADDRESS=${MORPH_PORTAL:-0x6900000000000000000000000000000000000001} - MORPH_NODE_L1_CONFIRMATIONS=0 - - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} + # - MORPH_NODE_SYNC_START_HEIGHT=${MORPH_NODE_SYNC_START_HEIGHT:-1} volumes: - ".devnet/node4:${NODE_DATA_DIR}" - "${PWD}/jwt-secret.txt:${JWT_SECRET_PATH}"