Skip to content

Commit 80467b2

Browse files
committed
add min delay verification
1 parent ce38349 commit 80467b2

File tree

3 files changed

+256
-33
lines changed

3 files changed

+256
-33
lines changed

miner/worker.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte
149149
chainExtra = params.GetExtra(w.chainConfig)
150150
)
151151

152-
timestamp, timestampMS := customheader.GetNextTimestamp(parent, tstart)
152+
timestamp, timestampMS, err := customheader.GetNextTimestamp(parent, chainExtra, tstart)
153+
if err != nil {
154+
return nil, fmt.Errorf("failed to get next timestamp: %w", err)
155+
}
153156

154157
header := &types.Header{
155158
ParentHash: parent.Hash(),

plugin/evm/customheader/time.go

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ var (
2424
ErrTimeMillisecondsRequired = errors.New("TimeMilliseconds is required after Granite activation")
2525
ErrTimeMillisecondsMismatched = errors.New("TimeMilliseconds does not match header.Time")
2626
ErrTimeMillisecondsBeforeGranite = errors.New("TimeMilliseconds should be nil before Granite activation")
27+
ErrMinDelayNotMet = errors.New("minimum block delay not met")
28+
ErrGraniteClockBehindParent = errors.New("current timestamp is not allowed to be behind than parent timestamp in Granite")
2729
)
2830

29-
// GetNextTimestamp calculates the timestamp (in seconds and milliseconds) for the next child block based on the parent's timestamp and the current time.
31+
// GetNextTimestamp calculates the timestamp (in seconds and milliseconds) for the header based on the parent's timestamp and the current time.
3032
// First return value is the timestamp in seconds, second return value is the timestamp in milliseconds.
31-
func GetNextTimestamp(parent *types.Header, now time.Time) (uint64, uint64) {
33+
func GetNextTimestamp(parent *types.Header, config *extras.ChainConfig, now time.Time) (uint64, uint64, error) {
3234
var (
3335
timestamp = uint64(now.Unix())
3436
timestampMS = uint64(now.UnixMilli())
@@ -37,17 +39,23 @@ func GetNextTimestamp(parent *types.Header, now time.Time) (uint64, uint64) {
3739
// the same timestamp as their parent. This allows more than one block to be produced
3840
// per second.
3941
parentExtra := customtypes.GetHeaderExtra(parent)
40-
if parent.Time >= timestamp ||
41-
(parentExtra.TimeMilliseconds != nil && *parentExtra.TimeMilliseconds >= timestampMS) {
42-
timestamp = parent.Time
43-
// If the parent has a TimeMilliseconds, use it. Otherwise, use the parent time * 1000.
44-
if parentExtra.TimeMilliseconds != nil {
45-
timestampMS = *parentExtra.TimeMilliseconds
46-
} else {
47-
timestampMS = parent.Time * 1000 // TODO: establish minimum time
42+
if parent.Time >= timestamp || (parentExtra.TimeMilliseconds != nil && *parentExtra.TimeMilliseconds >= timestampMS) {
43+
// In pre-Granite, blocks are allowed to have the same timestamp as their parent.
44+
// In Granite, there is a minimum delay enforced, and if the timestamp is the same as the parent,
45+
// the block will be rejected.
46+
// The block builder should have already waited enough time to meet the minimum delay.
47+
// This is to early-exit from the block building.
48+
if config.IsGranite(timestamp) {
49+
return 0, 0, ErrGraniteClockBehindParent
4850
}
51+
// Add the delay to the parent timestamp with seconds precision.
52+
timestamp = parent.Time
53+
// Actually we don't need to set timestampMS, because it will be not be set if this is not
54+
// Granite, but setting here for consistency.
55+
timestampMS = parent.Time * 1000
4956
}
50-
return timestamp, timestampMS
57+
58+
return timestamp, timestampMS, nil
5159
}
5260

5361
// VerifyTime verifies that the header's Time and TimeMilliseconds fields are
@@ -100,7 +108,6 @@ func VerifyTime(extraConfig *extras.ChainConfig, parent *types.Header, header *t
100108
}
101109

102110
// Verify TimeMilliseconds is not earlier than parent's TimeMilliseconds
103-
// TODO: Ensure minimum block delay is enforced
104111
if parentExtra.TimeMilliseconds != nil && *headerExtra.TimeMilliseconds < *parentExtra.TimeMilliseconds {
105112
return fmt.Errorf("%w: %d < parent %d",
106113
errBlockTooOld,
@@ -109,7 +116,14 @@ func VerifyTime(extraConfig *extras.ChainConfig, parent *types.Header, header *t
109116
)
110117
}
111118

119+
// Verify minimum block delay is enforced
120+
if err := verifyMinDelay(parent, header); err != nil {
121+
return err
122+
}
123+
112124
// Verify TimeMilliseconds is not too far in the future
125+
// Q: This still is a problem especially if someone issues a block in the future
126+
// then the next builder will wait until potentially MaxFutureBlockTime + delay
113127
if maxBlockTimeMillis := uint64(now.Add(MaxFutureBlockTime).UnixMilli()); *headerExtra.TimeMilliseconds > maxBlockTimeMillis {
114128
return fmt.Errorf("%w: %d > allowed %d",
115129
ErrBlockTooFarInFuture,
@@ -120,3 +134,38 @@ func VerifyTime(extraConfig *extras.ChainConfig, parent *types.Header, header *t
120134

121135
return nil
122136
}
137+
138+
// verifyMinDelay verifies that the minimum block delay is enforced based on the min delay excess.
139+
func verifyMinDelay(parent *types.Header, header *types.Header) error {
140+
headerExtra := customtypes.GetHeaderExtra(header)
141+
parentExtra := customtypes.GetHeaderExtra(parent)
142+
143+
// if parent has no TimeMilliseconds, no min delay is required
144+
if parentExtra.TimeMilliseconds == nil {
145+
return nil
146+
}
147+
148+
// Calculate the actual time difference in milliseconds
149+
actualDelayMillis := *headerExtra.TimeMilliseconds - *parentExtra.TimeMilliseconds
150+
151+
// Parent might not have a min delay excess if this is the first Granite block
152+
// in this case we cannot verify the min delay,
153+
// Otherwise parent should have been verified in VerifyMinDelayExcess
154+
minDelayExcess := customtypes.GetHeaderExtra(parent).MinDelayExcess
155+
if minDelayExcess == nil {
156+
return nil
157+
}
158+
159+
minRequiredDelayMillis := minDelayExcess.Delay()
160+
161+
// Check if the actual delay meets the minimum requirement
162+
if actualDelayMillis < minRequiredDelayMillis {
163+
return fmt.Errorf("%w: actual delay %dms < required %dms",
164+
ErrMinDelayNotMet,
165+
actualDelayMillis,
166+
minRequiredDelayMillis,
167+
)
168+
}
169+
170+
return nil
171+
}

0 commit comments

Comments
 (0)