Skip to content

Commit 0fc2022

Browse files
Extend fork ID to timestamp-based forks (#6324)
Starting from [Shanghai](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md), forks are based on timestamps rather than block heights (see PR #6238). This PR extends [EIP-2124](https://eips.ethereum.org/EIPS/eip-2124) Fork ID to include timestamp-based blocks. See also ethereum/go-ethereum#25878. Co-authored-by: Marius van der Wijden <[email protected]>
1 parent 56e64fc commit 0fc2022

File tree

21 files changed

+292
-150
lines changed

21 files changed

+292
-150
lines changed

cmd/observer/observer/server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ func makeForksENREntry(chain string) (enr.Entry, error) {
109109
return nil, fmt.Errorf("unknown chain %s", chain)
110110
}
111111

112-
forks := forkid.GatherForks(chainConfig)
113-
return eth.CurrentENREntryFromForks(forks, *genesisHash, 0), nil
112+
heightForks, timeForks := forkid.GatherForks(chainConfig)
113+
return eth.CurrentENREntryFromForks(heightForks, timeForks, *genesisHash, 0, 0), nil
114114
}
115115

116116
func (server *Server) Bootnodes() []*enode.Node {

cmd/rpcdaemon/commands/erigon_system.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import (
1313
// Forks is a data type to record a list of forks passed by this node
1414
type Forks struct {
1515
GenesisHash common.Hash `json:"genesis"`
16-
Forks []uint64 `json:"forks"`
16+
HeightForks []uint64 `json:"heightForks"`
17+
TimeForks []uint64 `json:"timeForks"`
1718
}
1819

1920
// Forks implements erigon_forks. Returns the genesis block hash and a sorted list of all forks block numbers
@@ -28,9 +29,9 @@ func (api *ErigonImpl) Forks(ctx context.Context) (Forks, error) {
2829
if err != nil {
2930
return Forks{}, err
3031
}
31-
forksBlocks := forkid.GatherForks(chainConfig)
32+
heightForks, timeForks := forkid.GatherForks(chainConfig)
3233

33-
return Forks{genesis.Hash(), forksBlocks}, nil
34+
return Forks{genesis.Hash(), heightForks, timeForks}, nil
3435
}
3536

3637
// Post the merge eth_blockNumber will return latest forkChoiceHead block number

cmd/sentry/sentry/eth_handshake.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,6 @@ func checkPeerStatusCompatibility(
7171
return fmt.Errorf("genesis hash does not match: theirs %x, ours %x", reply.Genesis, genesisHash)
7272
}
7373

74-
forks := make([]uint64, len(status.ForkData.Forks))
75-
// copy because forkid.NewFilterFromForks will write into this slice
76-
copy(forks, status.ForkData.Forks)
77-
forkFilter := forkid.NewFilterFromForks(forks, genesisHash, status.MaxBlock)
78-
if err := forkFilter(reply.ForkID); err != nil {
79-
return err
80-
}
81-
82-
return nil
74+
forkFilter := forkid.NewFilterFromForks(status.ForkData.HeightForks, status.ForkData.TimeForks, genesisHash, status.MaxBlockHeight, status.MaxBlockTime)
75+
return forkFilter(reply.ForkID)
8376
}

cmd/sentry/sentry/eth_handshake_test.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import (
77
"github.com/holiman/uint256"
88
"github.com/ledgerwatch/erigon-lib/gointerfaces"
99
proto_sentry "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry"
10+
"github.com/stretchr/testify/assert"
11+
1012
"github.com/ledgerwatch/erigon/common"
1113
"github.com/ledgerwatch/erigon/core/forkid"
1214
"github.com/ledgerwatch/erigon/eth/protocols/eth"
1315
"github.com/ledgerwatch/erigon/params"
14-
"github.com/stretchr/testify/assert"
1516
)
1617

1718
func TestCheckPeerStatusCompatibility(t *testing.T) {
@@ -23,17 +24,19 @@ func TestCheckPeerStatusCompatibility(t *testing.T) {
2324
TD: big.NewInt(0),
2425
Head: common.Hash{},
2526
Genesis: params.MainnetGenesisHash,
26-
ForkID: forkid.NewID(params.MainnetChainConfig, params.MainnetGenesisHash, 0),
27+
ForkID: forkid.NewID(params.MainnetChainConfig, params.MainnetGenesisHash, 0, 0),
2728
}
29+
heightForks, timeForks := forkid.GatherForks(params.MainnetChainConfig)
2830
status := proto_sentry.StatusData{
2931
NetworkId: networkID,
3032
TotalDifficulty: gointerfaces.ConvertUint256IntToH256(new(uint256.Int)),
3133
BestHash: nil,
3234
ForkData: &proto_sentry.Forks{
33-
Genesis: gointerfaces.ConvertHashToH256(params.MainnetGenesisHash),
34-
Forks: forkid.GatherForks(params.MainnetChainConfig),
35+
Genesis: gointerfaces.ConvertHashToH256(params.MainnetGenesisHash),
36+
HeightForks: heightForks,
37+
TimeForks: timeForks,
3538
},
36-
MaxBlock: 0,
39+
MaxBlockHeight: 0,
3740
}
3841

3942
t.Run("ok", func(t *testing.T) {

cmd/sentry/sentry/sentry_api.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import (
1818

1919
// Methods of sentry called by Core
2020

21-
func (cs *MultiClient) UpdateHead(ctx context.Context, height uint64, hash common.Hash, td *uint256.Int) {
21+
func (cs *MultiClient) UpdateHead(ctx context.Context, height, time uint64, hash common.Hash, td *uint256.Int) {
2222
cs.lock.Lock()
2323
defer cs.lock.Unlock()
2424
cs.headHeight = height
25+
cs.headTime = time
2526
cs.headHash = hash
2627
cs.headTd = td
2728
statusMsg := cs.makeStatusData()

cmd/sentry/sentry/sentry_grpc_server.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ func handShake(
292292
TD: ourTD.ToBig(),
293293
Head: gointerfaces.ConvertH256ToHash(status.BestHash),
294294
Genesis: genesisHash,
295-
ForkID: forkid.NewIDFromForks(status.ForkData.Forks, genesisHash, status.MaxBlock),
295+
ForkID: forkid.NewIDFromForks(status.ForkData.HeightForks, status.ForkData.TimeForks, genesisHash, status.MaxBlockHeight, status.MaxBlockTime),
296296
}
297297
errc <- p2p.Send(rw, eth.StatusMsg, s)
298298
}()
@@ -957,8 +957,8 @@ func (ss *GrpcServer) SetStatus(ctx context.Context, statusData *proto_sentry.St
957957
ss.P2pServer = srv
958958
}
959959

960-
ss.P2pServer.LocalNode().Set(eth.CurrentENREntryFromForks(statusData.ForkData.Forks, genesisHash, statusData.MaxBlock))
961-
if ss.statusData == nil || statusData.MaxBlock != 0 {
960+
ss.P2pServer.LocalNode().Set(eth.CurrentENREntryFromForks(statusData.ForkData.HeightForks, statusData.ForkData.TimeForks, genesisHash, statusData.MaxBlockHeight, statusData.MaxBlockTime))
961+
if ss.statusData == nil || statusData.MaxBlockHeight != 0 {
962962
// Not overwrite statusData if the message contains zero MaxBlock (comes from standalone transaction pool)
963963
ss.statusData = statusData
964964
}

cmd/sentry/sentry/sentry_grpc_server_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import (
1111
proto_sentry "github.com/ledgerwatch/erigon-lib/gointerfaces/sentry"
1212
"github.com/ledgerwatch/erigon-lib/kv"
1313
"github.com/ledgerwatch/erigon-lib/kv/memdb"
14+
"github.com/stretchr/testify/require"
15+
1416
"github.com/ledgerwatch/erigon/common"
1517
"github.com/ledgerwatch/erigon/core"
1618
"github.com/ledgerwatch/erigon/core/forkid"
1719
"github.com/ledgerwatch/erigon/core/rawdb"
1820
"github.com/ledgerwatch/erigon/eth/protocols/eth"
1921
"github.com/ledgerwatch/erigon/p2p"
2022
"github.com/ledgerwatch/erigon/params"
21-
"github.com/stretchr/testify/require"
2223
)
2324

2425
func testSentryServer(db kv.Getter, genesis *core.Genesis, genesisHash common.Hash) *GrpcServer {
@@ -34,14 +35,17 @@ func testSentryServer(db kv.Getter, genesis *core.Genesis, genesisHash common.Ha
3435

3536
headTd256 := new(uint256.Int)
3637
headTd256.SetFromBig(headTd)
38+
heightForks, timeForks := forkid.GatherForks(genesis.Config)
3739
s.statusData = &proto_sentry.StatusData{
3840
NetworkId: 1,
3941
TotalDifficulty: gointerfaces.ConvertUint256IntToH256(headTd256),
4042
BestHash: gointerfaces.ConvertHashToH256(head.Hash()),
41-
MaxBlock: head.Number.Uint64(),
43+
MaxBlockHeight: head.Number.Uint64(),
44+
MaxBlockTime: head.Time,
4245
ForkData: &proto_sentry.Forks{
43-
Genesis: gointerfaces.ConvertHashToH256(genesisHash),
44-
Forks: forkid.GatherForks(genesis.Config),
46+
Genesis: gointerfaces.ConvertHashToH256(genesisHash),
47+
HeightForks: heightForks,
48+
TimeForks: timeForks,
4549
},
4650
}
4751
return s
@@ -107,8 +111,8 @@ func testForkIDSplit(t *testing.T, protocol uint) {
107111
}
108112

109113
// Progress into Homestead. Fork's match, so we don't care what the future holds
110-
s1.statusData.MaxBlock = 1
111-
s2.statusData.MaxBlock = 1
114+
s1.statusData.MaxBlockHeight = 1
115+
s2.statusData.MaxBlockHeight = 1
112116

113117
go func() { errc <- handShake(ctx, s1.GetStatus(), [64]byte{1}, p2pNoFork, protocol, protocol, nil) }()
114118
go func() { errc <- handShake(ctx, s2.GetStatus(), [64]byte{2}, p2pProFork, protocol, protocol, nil) }()
@@ -125,8 +129,8 @@ func testForkIDSplit(t *testing.T, protocol uint) {
125129
}
126130

127131
// Progress into Spurious. Forks mismatch, signalling differing chains, reject
128-
s1.statusData.MaxBlock = 2
129-
s2.statusData.MaxBlock = 2
132+
s1.statusData.MaxBlockHeight = 2
133+
s2.statusData.MaxBlockHeight = 2
130134

131135
// Both nodes should allow the other to connect (same genesis, next fork is the same)
132136
go func() { errc <- handShake(ctx, s1.GetStatus(), [64]byte{1}, p2pNoFork, protocol, protocol, nil) }()

cmd/sentry/sentry/sentry_multi_client.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,12 @@ type MultiClient struct {
252252
nodeName string
253253
sentries []direct.SentryClient
254254
headHeight uint64
255+
headTime uint64
255256
headHash common.Hash
256257
headTd *uint256.Int
257258
ChainConfig *params.ChainConfig
258-
forks []uint64
259+
heightForks []uint64
260+
timeForks []uint64
259261
genesisHash common.Hash
260262
networkId uint64
261263
db kv.RwDB
@@ -311,12 +313,12 @@ func NewMultiClient(
311313
passivePeers: chainConfig.TerminalTotalDifficultyPassed,
312314
}
313315
cs.ChainConfig = chainConfig
314-
cs.forks = forkid.GatherForks(cs.ChainConfig)
316+
cs.heightForks, cs.timeForks = forkid.GatherForks(cs.ChainConfig)
315317
cs.genesisHash = genesisHash
316318
cs.networkId = networkID
317319
var err error
318320
err = db.View(context.Background(), func(tx kv.Tx) error {
319-
cs.headHeight, cs.headHash, cs.headTd, err = cs.Bd.UpdateFromDb(tx)
321+
cs.headHeight, cs.headTime, cs.headHash, cs.headTd, err = cs.Bd.UpdateFromDb(tx)
320322
return err
321323
})
322324
return cs, err
@@ -777,10 +779,12 @@ func (cs *MultiClient) makeStatusData() *proto_sentry.StatusData {
777779
NetworkId: s.networkId,
778780
TotalDifficulty: gointerfaces.ConvertUint256IntToH256(s.headTd),
779781
BestHash: gointerfaces.ConvertHashToH256(s.headHash),
780-
MaxBlock: s.headHeight,
782+
MaxBlockHeight: s.headHeight,
783+
MaxBlockTime: s.headTime,
781784
ForkData: &proto_sentry.Forks{
782-
Genesis: gointerfaces.ConvertHashToH256(s.genesisHash),
783-
Forks: s.forks,
785+
Genesis: gointerfaces.ConvertHashToH256(s.genesisHash),
786+
HeightForks: s.heightForks,
787+
TimeForks: s.timeForks,
784788
},
785789
PassivePeers: cs.passivePeers,
786790
}

common/sorted.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,13 @@ func SortedKeys[K constraints.Ordered, V any](m map[K]V) []K {
1515
slices.Sort(keys)
1616
return keys
1717
}
18+
19+
func RemoveDuplicatesFromSorted[T constraints.Ordered](slice []T) []T {
20+
for i := 1; i < len(slice); i++ {
21+
if slice[i] == slice[i-1] {
22+
slice = append(slice[:i], slice[i+1:]...)
23+
i--
24+
}
25+
}
26+
return slice
27+
}

common/sorted_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestRemoveDuplicatesFromSorted(t *testing.T) {
10+
tests := []struct {
11+
in []int
12+
out []int
13+
}{
14+
{[]int{}, []int{}},
15+
{[]int{1, 2, 5}, []int{1, 2, 5}},
16+
{[]int{1, 1, 2, 5, 5, 5, 7}, []int{1, 2, 5, 7}},
17+
{[]int{8, 8, 5, 4, 4, 4, 2}, []int{8, 5, 4, 2}},
18+
{[]int{8, 8, 8, 8, 8, 4}, []int{8, 4}},
19+
{[]int{1, 8, 8, 8, 8, 8}, []int{1, 8}},
20+
{[]int{8, 8, 8, 8, 8}, []int{8}},
21+
{[]int{-3, -3}, []int{-3}},
22+
{[]int{-3}, []int{-3}},
23+
}
24+
25+
for _, test := range tests {
26+
x := RemoveDuplicatesFromSorted(test.in)
27+
assert.Equal(t, test.out, x)
28+
}
29+
}

0 commit comments

Comments
 (0)