45
45
ErrLocalIncompatibleOrStale = errors .New ("local incompatible or needs update" )
46
46
)
47
47
48
+ // timestampThreshold is the Ethereum mainnet genesis timestamp. It is used to
49
+ // differentiate if a forkid.next field is a block number or a timestamp. Whilst
50
+ // very hacky, something's needed to split the validation during the transition
51
+ // period (block forks -> time forks).
52
+ const timestampThreshold = 1438269973
53
+
48
54
// Blockchain defines all necessary method to build a forkID.
49
55
type Blockchain interface {
50
56
// Config retrieves the chain's fork configuration.
@@ -72,35 +78,35 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) I
72
78
hash := crc32 .ChecksumIEEE (genesis [:])
73
79
74
80
// Calculate the current fork checksum and the next fork block
75
- forks , forksByTime := gatherForks (config )
76
- for _ , fork := range forks {
81
+ forksByBlock , forksByTime := gatherForks (config )
82
+ for _ , fork := range forksByBlock {
77
83
if fork <= head {
78
84
// Fork already passed, checksum the previous hash and the fork number
79
85
hash = checksumUpdate (hash , fork )
80
86
continue
81
87
}
82
88
return ID {Hash : checksumToBytes (hash ), Next : fork }
83
89
}
84
- var next uint64
85
90
for _ , fork := range forksByTime {
86
- if time >= fork {
87
- // Fork passed, checksum previous hash and fork time
91
+ if fork <= time {
92
+ // Fork already passed, checksum the previous hash and fork timestamp
88
93
hash = checksumUpdate (hash , fork )
89
94
continue
90
95
}
91
- next = fork
92
- break
96
+ return ID {Hash : checksumToBytes (hash ), Next : fork }
93
97
}
94
- return ID {Hash : checksumToBytes (hash ), Next : next }
98
+ return ID {Hash : checksumToBytes (hash ), Next : 0 }
95
99
}
96
100
97
101
// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance.
98
102
func NewIDWithChain (chain Blockchain ) ID {
103
+ head := chain .CurrentHeader ()
104
+
99
105
return NewID (
100
106
chain .Config (),
101
107
chain .Genesis ().Hash (),
102
- chain . CurrentHeader () .Number .Uint64 (),
103
- chain . CurrentHeader () .Time ,
108
+ head .Number .Uint64 (),
109
+ head .Time ,
104
110
)
105
111
}
106
112
@@ -111,7 +117,8 @@ func NewFilter(chain Blockchain) Filter {
111
117
chain .Config (),
112
118
chain .Genesis ().Hash (),
113
119
func () (uint64 , uint64 ) {
114
- return chain .CurrentHeader ().Number .Uint64 (), chain .CurrentHeader ().Time
120
+ head := chain .CurrentHeader ()
121
+ return head .Number .Uint64 (), head .Time
115
122
},
116
123
)
117
124
}
@@ -128,23 +135,23 @@ func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter {
128
135
func newFilter (config * params.ChainConfig , genesis common.Hash , headfn func () (uint64 , uint64 )) Filter {
129
136
// Calculate the all the valid fork hash and fork next combos
130
137
var (
131
- forks , forksByTime = gatherForks (config )
132
- sums = make ([][4 ]byte , len (forks )+ len (forksByTime )+ 1 ) // 0th is the genesis
138
+ forksByBlock , forksByTime = gatherForks (config )
139
+ forks = append (append ([]uint64 {}, forksByBlock ... ), forksByTime ... )
140
+ sums = make ([][4 ]byte , len (forks )+ 1 ) // 0th is the genesis
133
141
)
134
- allForks := append (forks , forksByTime ... )
135
142
hash := crc32 .ChecksumIEEE (genesis [:])
136
143
sums [0 ] = checksumToBytes (hash )
137
- for i , fork := range allForks {
144
+ for i , fork := range forks {
138
145
hash = checksumUpdate (hash , fork )
139
146
sums [i + 1 ] = checksumToBytes (hash )
140
147
}
141
148
// Add two sentries to simplify the fork checks and don't require special
142
149
// casing the last one.
150
+ forks = append (forks , math .MaxUint64 ) // Last fork will never be passed
143
151
if len (forksByTime ) == 0 {
144
- forks = append (forks , math .MaxUint64 )
152
+ // In purely block based forks, avoid the sentry spilling into timestapt territory
153
+ forksByBlock = append (forksByBlock , math .MaxUint64 ) // Last fork will never be passed
145
154
}
146
- forksByTime = append (forksByTime , math .MaxUint64 ) // Last fork will never be passed
147
-
148
155
// Create a validator that will filter out incompatible chains
149
156
return func (id ID ) error {
150
157
// Run the fork checksum validation ruleset:
@@ -166,33 +173,43 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u
166
173
// the remote, but at this current point in time we don't have enough
167
174
// information.
168
175
// 4. Reject in all other cases.
169
-
170
- verify := func (index int , headOrTime uint64 ) error {
176
+ block , time := headfn ()
177
+ for i , fork := range forks {
178
+ // Pick the head comparison based on fork progression
179
+ head := block
180
+ if i >= len (forksByBlock ) {
181
+ head = time
182
+ }
183
+ // If our head is beyond this fork, continue to the next (we have a dummy
184
+ // fork of maxuint64 as the last item to always fail this check eventually).
185
+ if head >= fork {
186
+ continue
187
+ }
171
188
// Found the first unpassed fork block, check if our current state matches
172
189
// the remote checksum (rule #1).
173
- if sums [index ] == id .Hash {
190
+ if sums [i ] == id .Hash {
174
191
// Fork checksum matched, check if a remote future fork block already passed
175
192
// locally without the local node being aware of it (rule #1a).
176
- if id .Next > 0 && headOrTime >= id .Next {
193
+ if id .Next > 0 && ( head >= id .Next || ( id . Next > timestampThreshold && time >= id . Next )) {
177
194
return ErrLocalIncompatibleOrStale
178
195
}
179
196
// Haven't passed locally a remote-only fork, accept the connection (rule #1b).
180
197
return nil
181
198
}
182
199
// The local and remote nodes are in different forks currently, check if the
183
200
// remote checksum is a subset of our local forks (rule #2).
184
- for j := 0 ; j < index ; j ++ {
201
+ for j := 0 ; j < i ; j ++ {
185
202
if sums [j ] == id .Hash {
186
203
// Remote checksum is a subset, validate based on the announced next fork
187
- if allForks [j ] != id .Next {
204
+ if forks [j ] != id .Next {
188
205
return ErrRemoteStale
189
206
}
190
207
return nil
191
208
}
192
209
}
193
210
// Remote chain is not a subset of our local one, check if it's a superset by
194
211
// any chance, signalling that we're simply out of sync (rule #3).
195
- for j := index + 1 ; j < len (sums ); j ++ {
212
+ for j := i + 1 ; j < len (sums ); j ++ {
196
213
if sums [j ] == id .Hash {
197
214
// Yay, remote checksum is a superset, ignore upcoming forks
198
215
return nil
@@ -201,27 +218,6 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u
201
218
// No exact, subset or superset match. We are on differing chains, reject.
202
219
return ErrLocalIncompatibleOrStale
203
220
}
204
-
205
- head , time := headfn ()
206
- // Verify forks by block
207
- for i , fork := range forks {
208
- // If our head is beyond this fork, continue to the next (we have a dummy
209
- // fork of maxuint64 as the last item to always fail this check eventually).
210
- if head >= fork {
211
- continue
212
- }
213
- return verify (i , head )
214
- }
215
- // Verify forks by time
216
- for i , fork := range forksByTime {
217
- // If our head is beyond this fork, continue to the next (we have a dummy
218
- // fork of maxuint64 as the last item to always fail this check eventually).
219
- if time >= fork {
220
- continue
221
- }
222
- return verify (len (forks )+ i , time )
223
- }
224
-
225
221
log .Error ("Impossible fork ID validation" , "id" , id )
226
222
return nil // Something's very wrong, accept rather than reject
227
223
}
@@ -242,45 +238,45 @@ func checksumToBytes(hash uint32) [4]byte {
242
238
return blob
243
239
}
244
240
245
- // gatherForks gathers all the known forks and creates a sorted list out of them.
241
+ // gatherForks gathers all the known forks and creates two sorted lists out of
242
+ // them, one for the block number based forks and the second for the timestamps.
246
243
func gatherForks (config * params.ChainConfig ) ([]uint64 , []uint64 ) {
247
244
// Gather all the fork block numbers via reflection
248
245
kind := reflect .TypeOf (params.ChainConfig {})
249
246
conf := reflect .ValueOf (config ).Elem ()
250
247
251
- var forks []uint64
252
- var forksByTime []uint64
248
+ var (
249
+ forksByBlock []uint64
250
+ forksByTime []uint64
251
+ )
253
252
for i := 0 ; i < kind .NumField (); i ++ {
254
253
// Fetch the next field and skip non-fork rules
255
254
field := kind .Field (i )
256
- time := false
257
- if ! strings .HasSuffix (field .Name , "Block" ) {
258
- if ! strings .HasSuffix (field .Name , "Time" ) {
259
- continue
260
- }
261
- time = true
255
+
256
+ time := strings .HasSuffix (field .Name , "Time" )
257
+ if ! time && ! strings .HasSuffix (field .Name , "Block" ) {
258
+ continue
262
259
}
263
260
if field .Type != reflect .TypeOf (new (big.Int )) {
264
261
continue
265
262
}
266
- // Extract the fork rule block number and aggregate it
263
+ // Extract the fork rule block number or timestamp and aggregate it
267
264
rule := conf .Field (i ).Interface ().(* big.Int )
268
265
if rule != nil {
269
266
if time {
270
267
forksByTime = append (forksByTime , rule .Uint64 ())
271
268
} else {
272
- forks = append (forks , rule .Uint64 ())
269
+ forksByBlock = append (forksByBlock , rule .Uint64 ())
273
270
}
274
271
}
275
272
}
276
-
277
- sort .Slice (forks , func (i , j int ) bool { return forks [i ] < forks [j ] })
273
+ sort .Slice (forksByBlock , func (i , j int ) bool { return forksByBlock [i ] < forksByBlock [j ] })
278
274
sort .Slice (forksByTime , func (i , j int ) bool { return forksByTime [i ] < forksByTime [j ] })
279
275
280
- // Deduplicate block numbers applying multiple forks
281
- for i := 1 ; i < len (forks ); i ++ {
282
- if forks [i ] == forks [i - 1 ] {
283
- forks = append (forks [:i ], forks [i + 1 :]... )
276
+ // Deduplicate fork identifiers applying multiple forks
277
+ for i := 1 ; i < len (forksByBlock ); i ++ {
278
+ if forksByBlock [i ] == forksByBlock [i - 1 ] {
279
+ forksByBlock = append (forksByBlock [:i ], forksByBlock [i + 1 :]... )
284
280
i --
285
281
}
286
282
}
@@ -291,11 +287,11 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) {
291
287
}
292
288
}
293
289
// Skip any forks in block 0, that's the genesis ruleset
294
- if len (forks ) > 0 && forks [0 ] == 0 {
295
- forks = forks [1 :]
290
+ if len (forksByBlock ) > 0 && forksByBlock [0 ] == 0 {
291
+ forksByBlock = forksByBlock [1 :]
296
292
}
297
293
if len (forksByTime ) > 0 && forksByTime [0 ] == 0 {
298
294
forksByTime = forksByTime [1 :]
299
295
}
300
- return forks , forksByTime
296
+ return forksByBlock , forksByTime
301
297
}
0 commit comments