Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2f9555e
support ms block generation
georgehao Apr 27, 2025
2844e18
update
georgehao Apr 27, 2025
bad6a6c
bump version
georgehao Apr 27, 2025
b8d0863
deadline to common sense
georgehao Apr 27, 2025
e282204
remove unused code
georgehao Apr 27, 2025
1e378fa
add logs
georgehao May 7, 2025
1748594
update
georgehao May 7, 2025
d088aff
update
georgehao May 7, 2025
a7e3c17
update
georgehao May 7, 2025
446492a
add more logs
georgehao May 7, 2025
ab1a331
add tryCommitNewWork log
georgehao May 9, 2025
d50ff4e
add more logs
georgehao May 9, 2025
680221d
rc10
georgehao May 13, 2025
13a6df5
address comments
georgehao Jul 23, 2025
646679a
resolve conflict
georgehao Jul 23, 2025
08f9a24
address comments
georgehao Jul 23, 2025
2e9d9f9
Merge remote-tracking branch 'origin/develop' into feat/support_ms_bl…
jonastheis Jul 31, 2025
3abad55
chore: auto version bump [bot]
jonastheis Jul 31, 2025
7c997fb
update logic
georgehao Aug 5, 2025
2b67b81
fix CalcTimestamp
georgehao Aug 5, 2025
fdbc4a5
add debug log
georgehao Aug 5, 2025
a964e2a
address comments
georgehao Aug 5, 2025
00f37de
update logic
georgehao Aug 5, 2025
586544c
fix lint
georgehao Aug 5, 2025
5cf9b4c
remove debug log
georgehao Aug 5, 2025
71dd65e
fix RelaxedPeriod
georgehao Aug 6, 2025
004f070
chore: auto version bump [bot]
georgehao Aug 6, 2025
0e51603
Merge branch 'develop' of github.com:scroll-tech/go-ethereum into fea…
georgehao Aug 6, 2025
9b8650c
update version
georgehao Aug 6, 2025
eb9f213
Merge branch 'feat/support_ms_block_generation' of github.com:scroll-…
georgehao Aug 6, 2025
4bd59ee
Merge branch 'develop' into feat/support_ms_block_generation
Thegaram Aug 11, 2025
5bc1c9f
make relaxed_period omitempty
georgehao Aug 11, 2025
0837abe
Merge branch 'feat/support_ms_block_generation' of github.com:scroll-…
georgehao Aug 11, 2025
826c3d9
fix omitempty
georgehao Aug 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion consensus/system_contract/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,48 @@ 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
// Get the base timestamp (in seconds)
timestamp := parent.Time

period := s.config.Period
if period == 0 {
period = 1 // Default to 1 second period
}

blocksPerSecond := CalcBlocksPerSecond(s.config.BlocksPerSecond)

// Calculate blocks per period
blocksPerPeriod := blocksPerSecond * period

// Calculate the next block number (the block we're about to create)
nextBlockNumber := parent.Number.Uint64() + 1

// Calculate the block index within the current period for the next block
blockIndex := nextBlockNumber % blocksPerPeriod

// If the next block is the first one in a new period, increment the timestamp by period
// blockIndex == 0 means we're starting a new period
if blockIndex == 0 && nextBlockNumber > 0 {
timestamp += 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
Expand Down
282 changes: 282 additions & 0 deletions consensus/system_contract/consensus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
package system_contract

import (
"math/big"
"testing"
"time"

"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/params"
)

func TestSystemContract_CalcTimestamp(t *testing.T) {
// Use a future timestamp to avoid the "timestamp < time.Now()" condition
futureTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future

tests := []struct {
name string
period uint64
blocksPerSecond uint64
parentTime uint64
parentNumber uint64
expectedTime uint64
description string
}{
{
name: "1 second period, 1 block per second - first block",
period: 1,
blocksPerSecond: 1,
parentTime: futureTime,
parentNumber: 0,
expectedTime: futureTime + 1, // Should increment by 1 second after 1 block
description: "First block in period, should increment timestamp",
},
{
name: "1 second period, 2 blocks per second - first block",
period: 1,
blocksPerSecond: 2,
parentTime: futureTime,
parentNumber: 0,
expectedTime: futureTime, // Should not increment yet (block 0, need block 1 to complete period)
description: "First of two blocks in period, should not increment timestamp yet",
},
{
name: "1 second period, 2 blocks per second - second block",
period: 1,
blocksPerSecond: 2,
parentTime: futureTime,
parentNumber: 1,
expectedTime: futureTime + 1, // Should increment by 1 second after 2 blocks
description: "Second of two blocks in period, should increment timestamp",
},
{
name: "2 second period, 1 block per second - first block",
period: 2,
blocksPerSecond: 1,
parentTime: futureTime,
parentNumber: 0,
expectedTime: futureTime, // Should not increment yet (need 2 blocks for 2-second period)
description: "First of two blocks in 2-second period",
},
{
name: "2 second period, 1 block per second - second block",
period: 2,
blocksPerSecond: 1,
parentTime: futureTime,
parentNumber: 1,
expectedTime: futureTime + 2, // Should increment by 2 seconds after 2 blocks
description: "Second of two blocks in 2-second period, should increment by period",
},
{
name: "2 second period, 2 blocks per second - fourth block",
period: 2,
blocksPerSecond: 2,
parentTime: futureTime,
parentNumber: 3, // blocks 0,1,2,3 = 4 blocks total for 2-second period
expectedTime: futureTime + 2, // Should increment by 2 seconds after 4 blocks
description: "Last block in 2-second period with 2 blocks/sec",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &params.SystemContractConfig{
Period: tt.period,
BlocksPerSecond: tt.blocksPerSecond,
RelaxedPeriod: false, // Ensure we test strict timing
}

sc := &SystemContract{
config: config,
}

parent := &types.Header{
Time: tt.parentTime,
Number: new(big.Int).SetUint64(tt.parentNumber),
}

result := sc.CalcTimestamp(parent)
if result != tt.expectedTime {
t.Errorf("%s: CalcTimestamp() = %d, want %d\nDescription: %s\nConfig: Period=%d, BlocksPerSecond=%d, ParentNumber=%d",
tt.name, result, tt.expectedTime, tt.description, tt.period, tt.blocksPerSecond, tt.parentNumber)
}
})
}
}

func TestBlockIntervalTiming(t *testing.T) {
tests := []struct {
name string
period uint64
blocksPerSecond uint64
expectedInterval time.Duration
description string
}{
{
name: "1 block per second",
period: 1,
blocksPerSecond: 1,
expectedInterval: 1000 * time.Millisecond,
description: "Traditional 1 second block time",
},
{
name: "2 blocks per second",
period: 1,
blocksPerSecond: 2,
expectedInterval: 500 * time.Millisecond,
description: "Fast blocks every 500ms",
},
{
name: "4 blocks per second",
period: 1,
blocksPerSecond: 4,
expectedInterval: 250 * time.Millisecond,
description: "Very fast blocks every 250ms",
},
{
name: "10 blocks per second",
period: 1,
blocksPerSecond: 10,
expectedInterval: 100 * time.Millisecond,
description: "Ultra fast blocks every 100ms",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
periodMs := CalcPeriodMs(tt.blocksPerSecond)
actualInterval := time.Duration(periodMs) * time.Millisecond

if actualInterval != tt.expectedInterval {
t.Errorf("%s: Block interval = %v, want %v\nDescription: %s",
tt.name, actualInterval, tt.expectedInterval, tt.description)
}

// Also verify the math: blocksPerSecond * interval should equal 1 second
totalTimePerSecond := time.Duration(tt.blocksPerSecond) * actualInterval
expectedTotalTime := 1 * time.Second

if totalTimePerSecond != expectedTotalTime {
t.Errorf("%s: %d blocks * %v interval = %v, want %v",
tt.name, tt.blocksPerSecond, actualInterval, totalTimePerSecond, expectedTotalTime)
}
})
}
}

func TestComplexTimingScenarios(t *testing.T) {
// Test a more complex scenario: tracking timestamp progression over multiple blocks
config := &params.SystemContractConfig{
Period: 1,
BlocksPerSecond: 4, // 250ms per block
RelaxedPeriod: false,
}

sc := &SystemContract{
config: config,
}

baseTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
expectedTimestamps := []uint64{
baseTime, // block 0: no increment yet
baseTime, // block 1: no increment yet
baseTime, // block 2: no increment yet
baseTime + 1, // block 3: complete period, increment by 1 second
baseTime + 1, // block 4: start new period
baseTime + 1, // block 5: continue period
baseTime + 1, // block 6: continue period
baseTime + 2, // block 7: complete second period, increment by 1 second
}

for i, expectedTime := range expectedTimestamps {
parent := &types.Header{
Time: baseTime,
Number: new(big.Int).SetUint64(uint64(i)),
}

result := sc.CalcTimestamp(parent)
if result != expectedTime {
t.Errorf("Block %d: CalcTimestamp() = %d, want %d", i, result, expectedTime)
}

// Update baseTime for next iteration to simulate time progression
baseTime = result
}
}

func TestDefaultValues(t *testing.T) {
// Test with default/zero values
config := &params.SystemContractConfig{
Period: 0, // Should default to 1
BlocksPerSecond: 0, // Should default to 1
RelaxedPeriod: false,
}

sc := &SystemContract{
config: config,
}

futureTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
parent := &types.Header{
Time: futureTime,
Number: new(big.Int).SetUint64(0),
}

result := sc.CalcTimestamp(parent)
// With defaults (period=1, blocksPerSecond=1), first block should increment timestamp
expected := futureTime + 1

if result != expected {
t.Errorf("With default values: CalcTimestamp() = %d, want %d", result, expected)
}

// Verify the period calculation
periodMs := CalcPeriodMs(0) // Should default to 1000ms
if periodMs != 1000 {
t.Errorf("Default period calculation: got %d ms, want 1000 ms", periodMs)
}
}

// TestTimestampIncrementLogic specifically tests the timestamp increment logic
func TestTimestampIncrementLogic(t *testing.T) {
config := &params.SystemContractConfig{
Period: 1,
BlocksPerSecond: 2, // 2 blocks per second
RelaxedPeriod: false,
}

sc := &SystemContract{
config: config,
}

baseTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
currentTime := baseTime

// Test for several blocks
for i := 0; i < 6; i++ {
parent := &types.Header{
Time: currentTime,
Number: new(big.Int).SetUint64(uint64(i)),
}

newTimestamp := sc.CalcTimestamp(parent)
nextBlockNumber := uint64(i + 1)

// Calculate expected timestamp
var expectedTimestamp uint64
if nextBlockNumber%2 == 0 {
// This is a period boundary (blocks 2, 4, 6, ...)
expectedTimestamp = currentTime + 1
} else {
// This is within a period (blocks 1, 3, 5, ...)
expectedTimestamp = currentTime
}

if newTimestamp != expectedTimestamp {
t.Errorf("Block %d: got timestamp %d, want %d", nextBlockNumber, newTimestamp, expectedTimestamp)
}

// Update currentTime for next iteration (simulate blockchain progression)
currentTime = newTimestamp
}
}
33 changes: 30 additions & 3 deletions miner/scroll_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,8 @@ func (w *worker) newWork(now time.Time, parent *types.Block, reorging bool, reor
// clique with relaxed period uses time.Now() as the header.Time, calculate the deadline
deadline = time.Unix(int64(header.Time+w.chainConfig.Clique.Period), 0)
}
if w.chainConfig.SystemContract != nil && w.chainConfig.SystemContract.RelaxedPeriod {
// 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 {
deadline = CalculateBlockDeadline(w.chainConfig.SystemContract, header)
}

w.current = &work{
Expand Down Expand Up @@ -1181,3 +1180,31 @@ 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 {
period := config.Period
if period == 0 {
period = 1 // Default to 1 second period
}

blocksPerSecond := system_contract.CalcBlocksPerSecond(config.BlocksPerSecond)
periodMs := system_contract.CalcPeriodMs(config.BlocksPerSecond)

// Calculate blocks per period
blocksPerPeriod := blocksPerSecond * period

// Calculate the actual timing based on block number within the current period
blockIndex := header.Number.Uint64() % blocksPerPeriod

// 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)
}
Loading
Loading