Skip to content

Commit

Permalink
p2p/sentry: sentry doesn't start with ErrNoHead (#10454)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
battlmonstr committed May 28, 2024
1 parent e9840ad commit 737164a
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 15 deletions.
3 changes: 3 additions & 0 deletions cmd/integration/commands/stages.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/ledgerwatch/secp256k1"
"github.com/spf13/cobra"

"golang.org/x/sync/errgroup"

Check failure on line 21 in cmd/integration/commands/stages.go

View workflow job for this annotation

GitHub Actions / tests-windows (windows-2022)

"golang.org/x/sync/errgroup" imported and not used

chain2 "github.com/ledgerwatch/erigon-lib/chain"
common2 "github.com/ledgerwatch/erigon-lib/common"
libcommon "github.com/ledgerwatch/erigon-lib/common"
Expand Down Expand Up @@ -1621,6 +1623,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 }
Expand Down
1 change: 1 addition & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,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
Expand Down
4 changes: 1 addition & 3 deletions p2p/sentry/sentry_multi_client/sentry_multi_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,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
}
Expand Down
53 changes: 41 additions & 12 deletions p2p/sentry/status_data_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,27 +33,59 @@ type StatusDataProvider struct {

networkId uint64
genesisHash libcommon.Hash
genesisHead ChainHead
heightForks []uint64
timeForks []uint64

logger log.Logger
}

func NewStatusDataProvider(
db kv.RoDB,
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())

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,
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions turbo/stages/mock/mock_sentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,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 }
Expand Down

0 comments on commit 737164a

Please sign in to comment.