diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 2dc32e09c..d245643f4 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -126,9 +126,9 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex // Sanity check the L1 origin was correctly selected to maintain the time invariant between L1 and L2 nextL2MilliTime := l2Parent.MillisecondTimestamp() + ba.rollupCfg.BlockTime - if nextL2MilliTime < l1Info.MilliTime() { + if nextL2MilliTime < l1Info.MillTimestamp() { return nil, NewResetError(fmt.Errorf("cannot build L2 block on top %s for time %d before L1 origin %s at time %d", - l2Parent, nextL2MilliTime, eth.ToBlockID(l1Info), l1Info.MilliTime())) + l2Parent, nextL2MilliTime, eth.ToBlockID(l1Info), l1Info.MillTimestamp())) } var upgradeTxs []hexutil.Bytes diff --git a/op-node/service.go b/op-node/service.go index fa54cf0bc..e36001fe5 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -27,6 +27,12 @@ import ( opflags "github.com/ethereum-optimism/optimism/op-service/flags" ) +const ( + MinBlockTimeSeconds = 1 + MaxBlockTimeSeconds = 3 + MaxBlockTimeMs = 750 +) + // NewConfig creates a Config from the provided flags or environment variables. func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { if err := flags.CheckRequired(ctx); err != nil { @@ -44,11 +50,11 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { } { - if rollupConfig.BlockTime >= 1 && rollupConfig.BlockTime <= 3 { + if rollupConfig.BlockTime >= MinBlockTimeSeconds && rollupConfig.BlockTime <= MaxBlockTimeSeconds { // Convert legacy second-level timestamp to millisecond timestamp, // This is a compatibility behavior. rollupConfig.BlockTime = rollupConfig.BlockTime * 1000 - } else if rollupConfig.BlockTime%50 != 0 && rollupConfig.BlockTime > 750 { + } else if rollupConfig.BlockTime%50 != 0 && rollupConfig.BlockTime > MaxBlockTimeMs { return nil, fmt.Errorf("block time is invalid, block_time: %v", rollupConfig.BlockTime) } // rollupConfig.BlockTime is millisecond block interval diff --git a/op-service/eth/block_info.go b/op-service/eth/block_info.go index f712beeed..5701d757b 100644 --- a/op-service/eth/block_info.go +++ b/op-service/eth/block_info.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" ) type BlockInfo interface { @@ -16,7 +17,8 @@ type BlockInfo interface { Root() common.Hash // state-root NumberU64() uint64 Time() uint64 - MilliTime() uint64 + MillTimestamp() uint64 + MillSeconds() uint64 // MixDigest field, reused for randomness after The Merge (Bellatrix hardfork) MixDigest() common.Hash BaseFee() *big.Int @@ -39,6 +41,7 @@ func InfoToL1BlockRef(info BlockInfo) L1BlockRef { Number: info.NumberU64(), ParentHash: info.ParentHash(), Time: info.Time(), + mTime: info.MillSeconds(), } } @@ -73,9 +76,15 @@ func (b blockInfo) ParentBeaconRoot() *common.Hash { return b.Block.BeaconRoot() } -func (b blockInfo) MilliTime() uint64 { - // TODO: adapt L1 timestamp - return b.Block.Time() * 1000 +func (b blockInfo) MillTimestamp() uint64 { + return b.Block.Time()*1000 + b.MillSeconds() +} + +func (b blockInfo) MillSeconds() uint64 { + if b.MixDigest() == (common.Hash{}) { + return 0 + } + return uint256.NewInt(0).SetBytes32(b.MixDigest().Bytes()).Uint64() } func BlockToInfo(b *types.Block) BlockInfo { @@ -108,9 +117,15 @@ func (h headerBlockInfo) Time() uint64 { return h.Header.Time } -func (h headerBlockInfo) MilliTime() uint64 { - // TODO: adapt L1 timestamp - return h.Header.Time * 1000 +func (h headerBlockInfo) MillTimestamp() uint64 { + return h.Header.Time*1000 + h.MillSeconds() +} + +func (h headerBlockInfo) MillSeconds() uint64 { + if h.MixDigest() == (common.Hash{}) { + return 0 + } + return uint256.NewInt(0).SetBytes32(h.MixDigest().Bytes()).Uint64() } func (h headerBlockInfo) MixDigest() common.Hash { diff --git a/op-service/eth/heads.go b/op-service/eth/heads.go index db837cbbe..b419c268b 100644 --- a/op-service/eth/heads.go +++ b/op-service/eth/heads.go @@ -5,9 +5,11 @@ import ( "time" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" ) // HeadSignalFn is used as callback function to accept head-signals @@ -43,11 +45,18 @@ func WatchHeadChanges(ctx context.Context, src NewHeadSource, fn HeadSignalFn) ( for { select { case header := <-headChanges: + var mTime uint64 + if header.MixDigest == (common.Hash{}) { + mTime = header.Time + } else { + mTime = uint256.NewInt(0).SetBytes32(header.MixDigest[:]).Uint64() + } fn(eventsCtx, L1BlockRef{ Hash: header.Hash(), Number: header.Number.Uint64(), ParentHash: header.ParentHash, - Time: header.Time, + Time: mTime, + mTime: mTime, }) case <-eventsCtx.Done(): return nil diff --git a/op-service/eth/id.go b/op-service/eth/id.go index e221508e3..7aa20cf45 100644 --- a/op-service/eth/id.go +++ b/op-service/eth/id.go @@ -59,12 +59,11 @@ type L1BlockRef struct { Number uint64 `json:"number"` ParentHash common.Hash `json:"parentHash"` Time uint64 `json:"timestamp"` - // TODO: + mTime uint64 `json:"timestamp"` // millisecond representation } func (id L1BlockRef) MilliTimestamp() uint64 { - // TODO: adapt L1 - return id.Time * 1000 + return id.Time*1000 + id.mTime } func (id L1BlockRef) String() string { diff --git a/op-service/sources/types.go b/op-service/sources/types.go index b57beb7db..05237e8e0 100644 --- a/op-service/sources/types.go +++ b/op-service/sources/types.go @@ -63,9 +63,15 @@ func (h headerInfo) Time() uint64 { return h.Header.Time } -func (h headerInfo) MilliTime() uint64 { - // TODO: adapt L1 timestamp - return h.Header.Time * 1000 +func (h headerInfo) MillTimestamp() uint64 { + return h.Header.Time*1000 + h.MillSeconds() +} + +func (h headerInfo) MillSeconds() uint64 { + if h.MixDigest() == (common.Hash{}) { + return 0 + } + return uint256.NewInt(0).SetBytes32(h.MixDigest().Bytes()).Uint64() } func (h headerInfo) MixDigest() common.Hash { diff --git a/op-service/testutils/l1info.go b/op-service/testutils/l1info.go index dcc45604d..c5d0cc1b3 100644 --- a/op-service/testutils/l1info.go +++ b/op-service/testutils/l1info.go @@ -21,6 +21,7 @@ type MockBlockInfo struct { InfoRoot common.Hash InfoNum uint64 InfoTime uint64 + InfoMTime uint64 InfoMixDigest [32]byte InfoBaseFee *big.Int InfoBlobBaseFee *big.Int @@ -56,10 +57,14 @@ func (l *MockBlockInfo) Time() uint64 { return l.InfoTime } -func (l *MockBlockInfo) MilliTime() uint64 { +func (l *MockBlockInfo) MillTimestamp() uint64 { return l.InfoTime * 1000 } +func (l *MockBlockInfo) MillSeconds() uint64 { + return l.InfoMTime +} + func (l *MockBlockInfo) MixDigest() common.Hash { return l.InfoMixDigest }