From a2437cda033ac546896e7883580ee4ca72df055d Mon Sep 17 00:00:00 2001 From: battlmonstr Date: Mon, 27 May 2024 10:43:13 +0200 Subject: [PATCH] p2p/sentry: sentry doesn't start with ErrNoHead (#10454) ReadCurrentHeaderHavingBody must never return nil, but IRL sometimes it does. In this case the node gets stuck, because it is not possible to start sentry and make progress. Use genesis data when rawdb.ReadCurrentHeaderHavingBody returns nil. --- cmd/integration/commands/stages.go | 4 +- eth/backend.go | 1 + .../sentry_multi_client.go | 4 +- p2p/sentry/status_data_provider.go | 53 ++++++++++++++----- turbo/stages/mock/mock_sentry.go | 1 + 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go index 925849f2ea0..220a9707fc1 100644 --- a/cmd/integration/commands/stages.go +++ b/cmd/integration/commands/stages.go @@ -19,6 +19,8 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/semaphore" + "golang.org/x/sync/errgroup" + chain2 "github.com/ledgerwatch/erigon-lib/chain" common2 "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -59,7 +61,6 @@ import ( "github.com/ledgerwatch/erigon/turbo/snapshotsync/freezeblocks" "github.com/ledgerwatch/erigon/turbo/snapshotsync/snap" stages2 "github.com/ledgerwatch/erigon/turbo/stages" - "golang.org/x/sync/errgroup" ) var cmdStageSnapshots = &cobra.Command{ @@ -1867,6 +1868,7 @@ func newSync(ctx context.Context, db kv.RwDB, miningConfig *params.MiningConfig, chainConfig, genesisBlock, chainConfig.ChainID.Uint64(), + logger, ) maxBlockBroadcastPeers := func(header *types.Header) uint { return 0 } diff --git a/eth/backend.go b/eth/backend.go index c9a943941f6..9b272994a9b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -586,6 +586,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger chainConfig, genesis, backend.config.NetworkID, + logger, ) // limit "new block" broadcasts to at most 10 random peers at time diff --git a/p2p/sentry/sentry_multi_client/sentry_multi_client.go b/p2p/sentry/sentry_multi_client/sentry_multi_client.go index d6d017b3dc1..109a1c5e81f 100644 --- a/p2p/sentry/sentry_multi_client/sentry_multi_client.go +++ b/p2p/sentry/sentry_multi_client/sentry_multi_client.go @@ -154,9 +154,7 @@ func SentryReconnectAndPumpStreamLoop[TMessage interface{}]( statusData, err := statusDataFactory(ctx) if err != nil { - if !errors.Is(err, sentry.ErrNoHead) { - logger.Error("SentryReconnectAndPumpStreamLoop: statusDataFactory error", "stream", streamName, "err", err) - } + logger.Error("SentryReconnectAndPumpStreamLoop: statusDataFactory error", "stream", streamName, "err", err) time.Sleep(time.Second) continue } diff --git a/p2p/sentry/status_data_provider.go b/p2p/sentry/status_data_provider.go index 5e0870353b8..12d23b1a6a8 100644 --- a/p2p/sentry/status_data_provider.go +++ b/p2p/sentry/status_data_provider.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/holiman/uint256" + "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/erigon-lib/chain" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -32,8 +33,11 @@ type StatusDataProvider struct { networkId uint64 genesisHash libcommon.Hash + genesisHead ChainHead heightForks []uint64 timeForks []uint64 + + logger log.Logger } func NewStatusDataProvider( @@ -41,11 +45,14 @@ func NewStatusDataProvider( chainConfig *chain.Config, genesis *types.Block, networkId uint64, + logger log.Logger, ) *StatusDataProvider { s := &StatusDataProvider{ db: db, networkId: networkId, genesisHash: genesis.Hash(), + genesisHead: makeGenesisChainHead(genesis), + logger: logger, } s.heightForks, s.timeForks = forkid.GatherForks(chainConfig, genesis.Time()) @@ -53,6 +60,32 @@ func NewStatusDataProvider( return s } +func uint256FromBigInt(num *big.Int) (*uint256.Int, error) { + if num == nil { + num = new(big.Int) + } + num256 := new(uint256.Int) + overflow := num256.SetFromBig(num) + if overflow { + return nil, fmt.Errorf("uint256FromBigInt: big.Int greater than 2^256-1") + } + return num256, nil +} + +func makeGenesisChainHead(genesis *types.Block) ChainHead { + genesisDifficulty, err := uint256FromBigInt(genesis.Difficulty()) + if err != nil { + panic(fmt.Errorf("makeGenesisChainHead: difficulty conversion error: %w", err)) + } + + return ChainHead{ + HeadHeight: genesis.NumberU64(), + HeadTime: genesis.Time(), + HeadHash: genesis.Hash(), + HeadTd: genesisDifficulty, + } +} + func (s *StatusDataProvider) makeStatusData(head ChainHead) *proto_sentry.StatusData { return &proto_sentry.StatusData{ NetworkId: s.networkId, @@ -71,6 +104,10 @@ func (s *StatusDataProvider) makeStatusData(head ChainHead) *proto_sentry.Status func (s *StatusDataProvider) GetStatusData(ctx context.Context) (*proto_sentry.StatusData, error) { chainHead, err := ReadChainHead(ctx, s.db) if err != nil { + if errors.Is(err, ErrNoHead) { + s.logger.Warn("sentry.StatusDataProvider: The canonical chain current header not found in the database. Check the database consistency. Using genesis as a fallback.") + return s.makeStatusData(s.genesisHead), nil + } return nil, err } return s.makeStatusData(chainHead), err @@ -84,23 +121,15 @@ func ReadChainHeadWithTx(tx kv.Tx) (ChainHead, error) { height := header.Number.Uint64() hash := header.Hash() - - var time uint64 - if header != nil { - time = header.Time - } + time := header.Time td, err := rawdb.ReadTd(tx, hash, height) if err != nil { return ChainHead{}, fmt.Errorf("ReadChainHead: ReadTd error at height %d and hash %s: %w", height, hash, err) } - if td == nil { - td = new(big.Int) - } - td256 := new(uint256.Int) - overflow := td256.SetFromBig(td) - if overflow { - return ChainHead{}, fmt.Errorf("ReadChainHead: total difficulty higher than 2^256-1") + td256, err := uint256FromBigInt(td) + if err != nil { + return ChainHead{}, fmt.Errorf("ReadChainHead: total difficulty conversion error: %w", err) } return ChainHead{height, time, hash, td256}, nil diff --git a/turbo/stages/mock/mock_sentry.go b/turbo/stages/mock/mock_sentry.go index c491cd9e4e4..41e7cc9494e 100644 --- a/turbo/stages/mock/mock_sentry.go +++ b/turbo/stages/mock/mock_sentry.go @@ -375,6 +375,7 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK mock.ChainConfig, mock.Genesis, mock.ChainConfig.ChainID.Uint64(), + logger, ) maxBlockBroadcastPeers := func(header *types.Header) uint { return 0 }