4545 ErrLocalIncompatibleOrStale = errors .New ("local incompatible or needs update" )
4646)
4747
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+
4854// Blockchain defines all necessary method to build a forkID.
4955type Blockchain interface {
5056 // Config retrieves the chain's fork configuration.
@@ -72,35 +78,35 @@ func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) I
7278 hash := crc32 .ChecksumIEEE (genesis [:])
7379
7480 // 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 {
7783 if fork <= head {
7884 // Fork already passed, checksum the previous hash and the fork number
7985 hash = checksumUpdate (hash , fork )
8086 continue
8187 }
8288 return ID {Hash : checksumToBytes (hash ), Next : fork }
8389 }
84- var next uint64
8590 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
8893 hash = checksumUpdate (hash , fork )
8994 continue
9095 }
91- next = fork
92- break
96+ return ID {Hash : checksumToBytes (hash ), Next : fork }
9397 }
94- return ID {Hash : checksumToBytes (hash ), Next : next }
98+ return ID {Hash : checksumToBytes (hash ), Next : 0 }
9599}
96100
97101// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance.
98102func NewIDWithChain (chain Blockchain ) ID {
103+ head := chain .CurrentHeader ()
104+
99105 return NewID (
100106 chain .Config (),
101107 chain .Genesis ().Hash (),
102- chain . CurrentHeader () .Number .Uint64 (),
103- chain . CurrentHeader () .Time ,
108+ head .Number .Uint64 (),
109+ head .Time ,
104110 )
105111}
106112
@@ -111,7 +117,8 @@ func NewFilter(chain Blockchain) Filter {
111117 chain .Config (),
112118 chain .Genesis ().Hash (),
113119 func () (uint64 , uint64 ) {
114- return chain .CurrentHeader ().Number .Uint64 (), chain .CurrentHeader ().Time
120+ head := chain .CurrentHeader ()
121+ return head .Number .Uint64 (), head .Time
115122 },
116123 )
117124}
@@ -128,23 +135,23 @@ func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter {
128135func newFilter (config * params.ChainConfig , genesis common.Hash , headfn func () (uint64 , uint64 )) Filter {
129136 // Calculate the all the valid fork hash and fork next combos
130137 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
133141 )
134- allForks := append (forks , forksByTime ... )
135142 hash := crc32 .ChecksumIEEE (genesis [:])
136143 sums [0 ] = checksumToBytes (hash )
137- for i , fork := range allForks {
144+ for i , fork := range forks {
138145 hash = checksumUpdate (hash , fork )
139146 sums [i + 1 ] = checksumToBytes (hash )
140147 }
141148 // Add two sentries to simplify the fork checks and don't require special
142149 // casing the last one.
150+ forks = append (forks , math .MaxUint64 ) // Last fork will never be passed
143151 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
145154 }
146- forksByTime = append (forksByTime , math .MaxUint64 ) // Last fork will never be passed
147-
148155 // Create a validator that will filter out incompatible chains
149156 return func (id ID ) error {
150157 // Run the fork checksum validation ruleset:
@@ -166,33 +173,43 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u
166173 // the remote, but at this current point in time we don't have enough
167174 // information.
168175 // 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+ }
171188 // Found the first unpassed fork block, check if our current state matches
172189 // the remote checksum (rule #1).
173- if sums [index ] == id .Hash {
190+ if sums [i ] == id .Hash {
174191 // Fork checksum matched, check if a remote future fork block already passed
175192 // 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 )) {
177194 return ErrLocalIncompatibleOrStale
178195 }
179196 // Haven't passed locally a remote-only fork, accept the connection (rule #1b).
180197 return nil
181198 }
182199 // The local and remote nodes are in different forks currently, check if the
183200 // 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 ++ {
185202 if sums [j ] == id .Hash {
186203 // Remote checksum is a subset, validate based on the announced next fork
187- if allForks [j ] != id .Next {
204+ if forks [j ] != id .Next {
188205 return ErrRemoteStale
189206 }
190207 return nil
191208 }
192209 }
193210 // Remote chain is not a subset of our local one, check if it's a superset by
194211 // 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 ++ {
196213 if sums [j ] == id .Hash {
197214 // Yay, remote checksum is a superset, ignore upcoming forks
198215 return nil
@@ -201,27 +218,6 @@ func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (u
201218 // No exact, subset or superset match. We are on differing chains, reject.
202219 return ErrLocalIncompatibleOrStale
203220 }
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-
225221 log .Error ("Impossible fork ID validation" , "id" , id )
226222 return nil // Something's very wrong, accept rather than reject
227223 }
@@ -242,45 +238,45 @@ func checksumToBytes(hash uint32) [4]byte {
242238 return blob
243239}
244240
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.
246243func gatherForks (config * params.ChainConfig ) ([]uint64 , []uint64 ) {
247244 // Gather all the fork block numbers via reflection
248245 kind := reflect .TypeOf (params.ChainConfig {})
249246 conf := reflect .ValueOf (config ).Elem ()
250247
251- var forks []uint64
252- var forksByTime []uint64
248+ var (
249+ forksByBlock []uint64
250+ forksByTime []uint64
251+ )
253252 for i := 0 ; i < kind .NumField (); i ++ {
254253 // Fetch the next field and skip non-fork rules
255254 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
262259 }
263260 if field .Type != reflect .TypeOf (new (big.Int )) {
264261 continue
265262 }
266- // Extract the fork rule block number and aggregate it
263+ // Extract the fork rule block number or timestamp and aggregate it
267264 rule := conf .Field (i ).Interface ().(* big.Int )
268265 if rule != nil {
269266 if time {
270267 forksByTime = append (forksByTime , rule .Uint64 ())
271268 } else {
272- forks = append (forks , rule .Uint64 ())
269+ forksByBlock = append (forksByBlock , rule .Uint64 ())
273270 }
274271 }
275272 }
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 ] })
278274 sort .Slice (forksByTime , func (i , j int ) bool { return forksByTime [i ] < forksByTime [j ] })
279275
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 :]... )
284280 i --
285281 }
286282 }
@@ -291,11 +287,11 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) {
291287 }
292288 }
293289 // 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 :]
296292 }
297293 if len (forksByTime ) > 0 && forksByTime [0 ] == 0 {
298294 forksByTime = forksByTime [1 :]
299295 }
300- return forks , forksByTime
296+ return forksByBlock , forksByTime
301297}
0 commit comments