SystemContract: support millisecond block generation (#1174)

* support ms block generation

* update

* bump version

* deadline to common sense

* remove unused code

* add logs

* update

* update

* update

* add more logs

* add tryCommitNewWork log

* add more logs

* rc10

* address comments

* address comments

* chore: auto version bump [bot]

* update logic

* fix CalcTimestamp

* add debug log

* address comments

* update logic

* fix lint

* remove debug log

* fix RelaxedPeriod

* chore: auto version bump [bot]

* update version

* make relaxed_period omitempty

* fix omitempty

---------

Co-authored-by: jonastheis <4181434+jonastheis@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
georgehao 2025-08-14 14:12:54 +08:00 committed by GitHub
parent 8bee78a76c
commit 2ee0371685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 176 additions and 8 deletions

View file

@ -227,8 +227,42 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types.
return nil
}
// CalcBlocksPerSecond returns the number of blocks per second
// Uses the BlocksPerSecond configuration parameter directly
// Default is 1 block per second if not specified
func CalcBlocksPerSecond(blocksPerSecond uint64) uint64 {
if blocksPerSecond == 0 {
return 1 // Default to 1 block per second
}
return blocksPerSecond
}
// CalcPeriodMs calculates the period in milliseconds between blocks
// based on the blocks per second configuration
func CalcPeriodMs(blocksPerSecond uint64) uint64 {
bps := CalcBlocksPerSecond(blocksPerSecond)
return 1000 / bps
}
func (s *SystemContract) CalcTimestamp(parent *types.Header) uint64 {
timestamp := parent.Time + s.config.Period
var timestamp uint64
if s.config.Period == 1 {
// Get the base timestamp (in seconds)
timestamp = parent.Time
blocksPerSecond := CalcBlocksPerSecond(s.config.BlocksPerSecond)
// Calculate the block index within the current period for the next block
blockIndex := parent.Number.Uint64() % blocksPerSecond
// If this block is the last one in the current second, increment the timestamp
// We compare with blocksPerSecond-1 because blockIndex is 0-based
if blockIndex == blocksPerSecond-1 {
timestamp++
}
} else {
timestamp = parent.Time + s.config.Period
}
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
// we don't know when it will be sealed

View file

@ -562,6 +562,9 @@ func (w *worker) newWork(now time.Time, parent *types.Block, reorging bool, reor
// system contract with relaxed period uses time.Now() as the header.Time, calculate the deadline
deadline = time.Unix(int64(header.Time+w.chainConfig.SystemContract.Period), 0)
}
if w.chainConfig.SystemContract != nil && w.chainConfig.SystemContract.Period == 1 {
deadline = CalculateBlockDeadline(w.chainConfig.SystemContract, header)
}
w.current = &work{
deadlineTimer: time.NewTimer(time.Until(deadline)),
@ -1181,3 +1184,23 @@ func (w *worker) handleReorg(trigger *reorgTrigger) error {
func (w *worker) isCanonical(header *types.Header) bool {
return w.chain.GetBlockByNumber(header.Number.Uint64()).Hash() == header.Hash()
}
// CalculateBlockDeadline calculates the deadline for block production based on
// SystemContract configuration and current header information.
// This function abstracts the deadline calculation logic for easier testing.
func CalculateBlockDeadline(config *params.SystemContractConfig, header *types.Header) time.Time {
blocksPerSecond := system_contract.CalcBlocksPerSecond(config.BlocksPerSecond)
periodMs := system_contract.CalcPeriodMs(blocksPerSecond)
// Calculate the actual timing based on block number within the current period
blockIndex := header.Number.Uint64() % blocksPerSecond
// Calculate base time and add the fraction based on block index within the period
baseTimeNano := int64(header.Time) * int64(time.Second)
fractionNano := int64(blockIndex) * int64(periodMs) * int64(time.Millisecond)
// Add one period to determine the deadline
nextBlockNano := baseTimeNano + fractionNano + int64(periodMs)*int64(time.Millisecond)
return time.Unix(0, nextBlockNano)
}

View file

@ -1468,3 +1468,113 @@ func TestEuclidV2TransitionVerification(t *testing.T) {
_, err = chain.InsertChain(blocks)
assert.NoError(t, err)
}
// TestBlockIntervalWithWorkerDeadline tests the block interval calculation
// that simulates the actual worker deadline calculation logic
func TestBlockIntervalWithWorkerDeadline(t *testing.T) {
tests := []struct {
name string
period uint64
blocksPerSecond uint64
expectedInterval time.Duration
blocks int // number of blocks to simulate
}{
{
name: "1 second period, 1 block per second",
period: 1,
blocksPerSecond: 1,
expectedInterval: 1000 * time.Millisecond,
blocks: 5,
},
{
name: "1 second period, 2 blocks per second",
period: 1,
blocksPerSecond: 2,
expectedInterval: 500 * time.Millisecond,
blocks: 6,
},
{
name: "1 second period, 4 blocks per second",
period: 1,
blocksPerSecond: 4,
expectedInterval: 250 * time.Millisecond,
blocks: 8,
},
{
name: "2 second period, 2 blocks per second",
period: 2,
blocksPerSecond: 2,
expectedInterval: 500 * time.Millisecond,
blocks: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &params.SystemContractConfig{
Period: tt.period,
BlocksPerSecond: tt.blocksPerSecond,
RelaxedPeriod: false,
}
// Start with a future timestamp to avoid time.Now() interference
currentTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
var deadlines []time.Time
var timestamps []uint64
for i := 0; i < tt.blocks; i++ {
// Create header for current block
header := &types.Header{
Time: currentTime,
Number: new(big.Int).SetUint64(uint64(i)),
}
// Simulate the worker deadline calculation logic from newWork
deadline := CalculateBlockDeadline(config, header)
deadlines = append(deadlines, deadline)
// Simulate timestamp calculation manually for next iteration
// This mimics the CalcTimestamp logic but simplified for testing
blocksPerSecond := system_contract.CalcBlocksPerSecond(tt.blocksPerSecond)
blocksPerPeriod := blocksPerSecond * tt.period
nextBlockNumber := uint64(i + 1)
var newTimestamp uint64
if nextBlockNumber%blocksPerPeriod == 0 && nextBlockNumber > 0 {
// Period boundary - increment timestamp
newTimestamp = currentTime + tt.period
} else {
// Within period - keep same timestamp
newTimestamp = currentTime
}
timestamps = append(timestamps, newTimestamp)
// Update currentTime for next iteration (simulate blockchain progression)
currentTime = newTimestamp
}
// Verify the intervals between deadlines
for i := 1; i < len(deadlines); i++ {
interval := deadlines[i].Sub(deadlines[i-1])
// Allow small tolerance for timing precision
tolerance := 10 * time.Millisecond
if interval < tt.expectedInterval-tolerance || interval > tt.expectedInterval+tolerance {
t.Errorf("Block %d interval: got %v, want %v (±%v)",
i, interval, tt.expectedInterval, tolerance)
}
}
// Note: Timestamp progression logic is verified in TestTimestampIncrementLogic
// Here we focus on deadline intervals which is what really matters for block production timing
blocksPerSecond := system_contract.CalcBlocksPerSecond(tt.blocksPerSecond)
blocksPerPeriod := blocksPerSecond * tt.period
t.Logf("Test %s completed successfully:", tt.name)
t.Logf(" - Expected interval: %v", tt.expectedInterval)
t.Logf(" - Blocks per period: %d", blocksPerPeriod)
t.Logf(" - Period length: %d seconds", tt.period)
})
}
}

View file

@ -825,7 +825,7 @@ func (c *CliqueConfig) String() string {
// SystemContractConfig is the consensus engine configs for rollup sequencer sealing.
type SystemContractConfig struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
BlocksPerSecond uint64 `json:"blocks_per_second,omitempty"` // Number of blocks per second within the period
SystemContractAddress common.Address `json:"system_contract_address"` // address of system contract on L1
SystemContractSlot common.Hash `json:"system_contract_slot"` // slot of signer address in system contract on L1

View file

@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 9 // Minor version component of the current release
VersionPatch = 2 // Patch version component of the current release
VersionPatch = 3 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

View file

@ -5,8 +5,8 @@ go 1.22
replace github.com/scroll-tech/go-ethereum => ../../..
require (
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1
github.com/scroll-tech/go-ethereum v1.10.14-0.20250305151038-478940e79601
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
github.com/scroll-tech/go-ethereum v1.10.14-0.20250625112225-a67863c65587
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
gorm.io/driver/postgres v1.5.7

View file

@ -172,6 +172,7 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 h1:Dhd58LE1D+dnoxpgLVeQBMF9uweL/fhQfZHWtWSiOlE=
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY=
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=