Skip to content

Commit 7c997fb

Browse files
committed
update logic
1 parent 3abad55 commit 7c997fb

File tree

5 files changed

+453
-28
lines changed

5 files changed

+453
-28
lines changed

consensus/system_contract/consensus.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,30 +227,47 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types.
227227
return nil
228228
}
229229

230-
// CalcBlocksPerSecond converts period to milliseconds and calculate blocks per second
231-
// For example: if Period = 250ms = 0.25s, then periodMs = 250
232-
func CalcBlocksPerSecond(periodMs uint64) uint64 {
233-
blocksPerSecond := 1000 / periodMs // integer division, e.g. 1000/250 = 4
230+
// CalcBlocksPerSecond returns the number of blocks per second
231+
// Uses the BlocksPerSecond configuration parameter directly
232+
// Default is 1 block per second if not specified
233+
func CalcBlocksPerSecond(blocksPerSecond uint64) uint64 {
234234
if blocksPerSecond == 0 {
235-
blocksPerSecond = 1
235+
return 1 // Default to 1 block per second
236236
}
237237
return blocksPerSecond
238238
}
239239

240+
// CalcPeriodMs calculates the period in milliseconds between blocks
241+
// based on the blocks per second configuration
242+
func CalcPeriodMs(blocksPerSecond uint64) uint64 {
243+
bps := CalcBlocksPerSecond(blocksPerSecond)
244+
return 1000 / bps
245+
}
246+
240247
func (s *SystemContract) CalcTimestamp(parent *types.Header) uint64 {
241248
// Get the base timestamp (in seconds)
242249
timestamp := parent.Time
243250

244-
periodMs := s.config.Period
245-
blocksPerSecond := CalcBlocksPerSecond(periodMs)
251+
period := s.config.Period
252+
if period == 0 {
253+
period = 1 // Default to 1 second period
254+
}
255+
256+
blocksPerSecond := CalcBlocksPerSecond(s.config.BlocksPerSecond)
257+
258+
// Calculate blocks per period
259+
blocksPerPeriod := blocksPerSecond * period
260+
261+
// Calculate the next block number (the block we're about to create)
262+
nextBlockNumber := parent.Number.Uint64() + 1
246263

247-
// Calculate the block index within the current second
248-
blockIndex := parent.Number.Uint64() % blocksPerSecond
264+
// Calculate the block index within the current period for the next block
265+
blockIndex := nextBlockNumber % blocksPerPeriod
249266

250-
// If this block is the last one in the current second, increment the timestamp
251-
// We compare with blocksPerSecond-1 because blockIndex is 0-based
252-
if blockIndex == blocksPerSecond-1 {
253-
timestamp++
267+
// If the next block is the first one in a new period, increment the timestamp by period
268+
// blockIndex == 0 means we're starting a new period
269+
if blockIndex == 0 && nextBlockNumber > 0 {
270+
timestamp += period
254271
}
255272

256273
// If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
package system_contract
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
"time"
7+
8+
"github.com/scroll-tech/go-ethereum/core/types"
9+
"github.com/scroll-tech/go-ethereum/params"
10+
)
11+
12+
func TestSystemContract_CalcTimestamp(t *testing.T) {
13+
// Use a future timestamp to avoid the "timestamp < time.Now()" condition
14+
futureTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
15+
16+
tests := []struct {
17+
name string
18+
period uint64
19+
blocksPerSecond uint64
20+
parentTime uint64
21+
parentNumber uint64
22+
expectedTime uint64
23+
description string
24+
}{
25+
{
26+
name: "1 second period, 1 block per second - first block",
27+
period: 1,
28+
blocksPerSecond: 1,
29+
parentTime: futureTime,
30+
parentNumber: 0,
31+
expectedTime: futureTime + 1, // Should increment by 1 second after 1 block
32+
description: "First block in period, should increment timestamp",
33+
},
34+
{
35+
name: "1 second period, 2 blocks per second - first block",
36+
period: 1,
37+
blocksPerSecond: 2,
38+
parentTime: futureTime,
39+
parentNumber: 0,
40+
expectedTime: futureTime, // Should not increment yet (block 0, need block 1 to complete period)
41+
description: "First of two blocks in period, should not increment timestamp yet",
42+
},
43+
{
44+
name: "1 second period, 2 blocks per second - second block",
45+
period: 1,
46+
blocksPerSecond: 2,
47+
parentTime: futureTime,
48+
parentNumber: 1,
49+
expectedTime: futureTime + 1, // Should increment by 1 second after 2 blocks
50+
description: "Second of two blocks in period, should increment timestamp",
51+
},
52+
{
53+
name: "2 second period, 1 block per second - first block",
54+
period: 2,
55+
blocksPerSecond: 1,
56+
parentTime: futureTime,
57+
parentNumber: 0,
58+
expectedTime: futureTime, // Should not increment yet (need 2 blocks for 2-second period)
59+
description: "First of two blocks in 2-second period",
60+
},
61+
{
62+
name: "2 second period, 1 block per second - second block",
63+
period: 2,
64+
blocksPerSecond: 1,
65+
parentTime: futureTime,
66+
parentNumber: 1,
67+
expectedTime: futureTime + 2, // Should increment by 2 seconds after 2 blocks
68+
description: "Second of two blocks in 2-second period, should increment by period",
69+
},
70+
{
71+
name: "2 second period, 2 blocks per second - fourth block",
72+
period: 2,
73+
blocksPerSecond: 2,
74+
parentTime: futureTime,
75+
parentNumber: 3, // blocks 0,1,2,3 = 4 blocks total for 2-second period
76+
expectedTime: futureTime + 2, // Should increment by 2 seconds after 4 blocks
77+
description: "Last block in 2-second period with 2 blocks/sec",
78+
},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
config := &params.SystemContractConfig{
84+
Period: tt.period,
85+
BlocksPerSecond: tt.blocksPerSecond,
86+
RelaxedPeriod: false, // Ensure we test strict timing
87+
}
88+
89+
sc := &SystemContract{
90+
config: config,
91+
}
92+
93+
parent := &types.Header{
94+
Time: tt.parentTime,
95+
Number: new(big.Int).SetUint64(tt.parentNumber),
96+
}
97+
98+
result := sc.CalcTimestamp(parent)
99+
if result != tt.expectedTime {
100+
t.Errorf("%s: CalcTimestamp() = %d, want %d\nDescription: %s\nConfig: Period=%d, BlocksPerSecond=%d, ParentNumber=%d",
101+
tt.name, result, tt.expectedTime, tt.description, tt.period, tt.blocksPerSecond, tt.parentNumber)
102+
}
103+
})
104+
}
105+
}
106+
107+
func TestBlockIntervalTiming(t *testing.T) {
108+
tests := []struct {
109+
name string
110+
period uint64
111+
blocksPerSecond uint64
112+
expectedInterval time.Duration
113+
description string
114+
}{
115+
{
116+
name: "1 block per second",
117+
period: 1,
118+
blocksPerSecond: 1,
119+
expectedInterval: 1000 * time.Millisecond,
120+
description: "Traditional 1 second block time",
121+
},
122+
{
123+
name: "2 blocks per second",
124+
period: 1,
125+
blocksPerSecond: 2,
126+
expectedInterval: 500 * time.Millisecond,
127+
description: "Fast blocks every 500ms",
128+
},
129+
{
130+
name: "4 blocks per second",
131+
period: 1,
132+
blocksPerSecond: 4,
133+
expectedInterval: 250 * time.Millisecond,
134+
description: "Very fast blocks every 250ms",
135+
},
136+
{
137+
name: "10 blocks per second",
138+
period: 1,
139+
blocksPerSecond: 10,
140+
expectedInterval: 100 * time.Millisecond,
141+
description: "Ultra fast blocks every 100ms",
142+
},
143+
}
144+
145+
for _, tt := range tests {
146+
t.Run(tt.name, func(t *testing.T) {
147+
periodMs := CalcPeriodMs(tt.blocksPerSecond)
148+
actualInterval := time.Duration(periodMs) * time.Millisecond
149+
150+
if actualInterval != tt.expectedInterval {
151+
t.Errorf("%s: Block interval = %v, want %v\nDescription: %s",
152+
tt.name, actualInterval, tt.expectedInterval, tt.description)
153+
}
154+
155+
// Also verify the math: blocksPerSecond * interval should equal 1 second
156+
totalTimePerSecond := time.Duration(tt.blocksPerSecond) * actualInterval
157+
expectedTotalTime := 1 * time.Second
158+
159+
if totalTimePerSecond != expectedTotalTime {
160+
t.Errorf("%s: %d blocks * %v interval = %v, want %v",
161+
tt.name, tt.blocksPerSecond, actualInterval, totalTimePerSecond, expectedTotalTime)
162+
}
163+
})
164+
}
165+
}
166+
167+
func TestComplexTimingScenarios(t *testing.T) {
168+
// Test a more complex scenario: tracking timestamp progression over multiple blocks
169+
config := &params.SystemContractConfig{
170+
Period: 1,
171+
BlocksPerSecond: 4, // 250ms per block
172+
RelaxedPeriod: false,
173+
}
174+
175+
sc := &SystemContract{
176+
config: config,
177+
}
178+
179+
baseTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
180+
expectedTimestamps := []uint64{
181+
baseTime, // block 0: no increment yet
182+
baseTime, // block 1: no increment yet
183+
baseTime, // block 2: no increment yet
184+
baseTime + 1, // block 3: complete period, increment by 1 second
185+
baseTime + 1, // block 4: start new period
186+
baseTime + 1, // block 5: continue period
187+
baseTime + 1, // block 6: continue period
188+
baseTime + 2, // block 7: complete second period, increment by 1 second
189+
}
190+
191+
for i, expectedTime := range expectedTimestamps {
192+
parent := &types.Header{
193+
Time: baseTime,
194+
Number: new(big.Int).SetUint64(uint64(i)),
195+
}
196+
197+
result := sc.CalcTimestamp(parent)
198+
if result != expectedTime {
199+
t.Errorf("Block %d: CalcTimestamp() = %d, want %d", i, result, expectedTime)
200+
}
201+
202+
// Update baseTime for next iteration to simulate time progression
203+
baseTime = result
204+
}
205+
}
206+
207+
func TestDefaultValues(t *testing.T) {
208+
// Test with default/zero values
209+
config := &params.SystemContractConfig{
210+
Period: 0, // Should default to 1
211+
BlocksPerSecond: 0, // Should default to 1
212+
RelaxedPeriod: false,
213+
}
214+
215+
sc := &SystemContract{
216+
config: config,
217+
}
218+
219+
futureTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
220+
parent := &types.Header{
221+
Time: futureTime,
222+
Number: new(big.Int).SetUint64(0),
223+
}
224+
225+
result := sc.CalcTimestamp(parent)
226+
// With defaults (period=1, blocksPerSecond=1), first block should increment timestamp
227+
expected := futureTime + 1
228+
229+
if result != expected {
230+
t.Errorf("With default values: CalcTimestamp() = %d, want %d", result, expected)
231+
}
232+
233+
// Verify the period calculation
234+
periodMs := CalcPeriodMs(0) // Should default to 1000ms
235+
if periodMs != 1000 {
236+
t.Errorf("Default period calculation: got %d ms, want 1000 ms", periodMs)
237+
}
238+
}
239+
240+
// TestTimestampIncrementLogic specifically tests the timestamp increment logic
241+
func TestTimestampIncrementLogic(t *testing.T) {
242+
config := &params.SystemContractConfig{
243+
Period: 1,
244+
BlocksPerSecond: 2, // 2 blocks per second
245+
RelaxedPeriod: false,
246+
}
247+
248+
sc := &SystemContract{
249+
config: config,
250+
}
251+
252+
baseTime := uint64(time.Now().Unix()) + 3600 // 1 hour in the future
253+
currentTime := baseTime
254+
255+
// Test for several blocks
256+
for i := 0; i < 6; i++ {
257+
parent := &types.Header{
258+
Time: currentTime,
259+
Number: new(big.Int).SetUint64(uint64(i)),
260+
}
261+
262+
newTimestamp := sc.CalcTimestamp(parent)
263+
nextBlockNumber := uint64(i + 1)
264+
265+
// Calculate expected timestamp
266+
var expectedTimestamp uint64
267+
if nextBlockNumber%2 == 0 {
268+
// This is a period boundary (blocks 2, 4, 6, ...)
269+
expectedTimestamp = currentTime + 1
270+
} else {
271+
// This is within a period (blocks 1, 3, 5, ...)
272+
expectedTimestamp = currentTime
273+
}
274+
275+
if newTimestamp != expectedTimestamp {
276+
t.Errorf("Block %d: got timestamp %d, want %d", nextBlockNumber, newTimestamp, expectedTimestamp)
277+
}
278+
279+
// Update currentTime for next iteration (simulate blockchain progression)
280+
currentTime = newTimestamp
281+
}
282+
}

miner/scroll_worker.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -559,19 +559,7 @@ func (w *worker) newWork(now time.Time, parent *types.Block, reorging bool, reor
559559
deadline = time.Unix(int64(header.Time+w.chainConfig.Clique.Period), 0)
560560
}
561561
if w.chainConfig.SystemContract != nil {
562-
periodMs := w.chainConfig.SystemContract.Period
563-
blocksPerSecond := system_contract.CalcBlocksPerSecond(periodMs)
564-
// Calculate the actual timing based on block number within the current second
565-
blockIndex := header.Number.Uint64() % blocksPerSecond
566-
567-
// Calculate base time and add the fraction of a second based on block index
568-
baseTimeNano := int64(header.Time) * int64(time.Second)
569-
fractionNano := int64(blockIndex) * int64(periodMs) * int64(time.Millisecond)
570-
571-
// Add one period to determine the deadline
572-
nextBlockNano := baseTimeNano + fractionNano + int64(periodMs)*int64(time.Millisecond)
573-
574-
deadline = time.Unix(0, nextBlockNano)
562+
deadline = CalculateBlockDeadline(w.chainConfig.SystemContract, header)
575563
}
576564

577565
w.current = &work{
@@ -1192,3 +1180,31 @@ func (w *worker) handleReorg(trigger *reorgTrigger) error {
11921180
func (w *worker) isCanonical(header *types.Header) bool {
11931181
return w.chain.GetBlockByNumber(header.Number.Uint64()).Hash() == header.Hash()
11941182
}
1183+
1184+
// CalculateBlockDeadline calculates the deadline for block production based on
1185+
// SystemContract configuration and current header information.
1186+
// This function abstracts the deadline calculation logic for easier testing.
1187+
func CalculateBlockDeadline(config *params.SystemContractConfig, header *types.Header) time.Time {
1188+
period := config.Period
1189+
if period == 0 {
1190+
period = 1 // Default to 1 second period
1191+
}
1192+
1193+
blocksPerSecond := system_contract.CalcBlocksPerSecond(config.BlocksPerSecond)
1194+
periodMs := system_contract.CalcPeriodMs(config.BlocksPerSecond)
1195+
1196+
// Calculate blocks per period
1197+
blocksPerPeriod := blocksPerSecond * period
1198+
1199+
// Calculate the actual timing based on block number within the current period
1200+
blockIndex := header.Number.Uint64() % blocksPerPeriod
1201+
1202+
// Calculate base time and add the fraction based on block index within the period
1203+
baseTimeNano := int64(header.Time) * int64(time.Second)
1204+
fractionNano := int64(blockIndex) * int64(periodMs) * int64(time.Millisecond)
1205+
1206+
// Add one period to determine the deadline
1207+
nextBlockNano := baseTimeNano + fractionNano + int64(periodMs)*int64(time.Millisecond)
1208+
1209+
return time.Unix(0, nextBlockNano)
1210+
}

0 commit comments

Comments
 (0)