Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wait miner finish the later multi-proposals when restarting the node; #2845

Merged
merged 4 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,6 @@ type PoSA interface {
GetFinalizedHeader(chain ChainHeaderReader, header *types.Header) *types.Header
VerifyVote(chain ChainHeaderReader, vote *types.VoteEnvelope) error
IsActiveValidatorAt(chain ChainHeaderReader, header *types.Header, checkVoteKeyFn func(bLSPublicKey *types.BLSPublicKey) bool) bool
BlockInterval() uint64
NextProposalBlock(chain ChainHeaderReader, header *types.Header, proposer common.Address) (uint64, uint64, error)
}
13 changes: 13 additions & 0 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -2123,6 +2123,19 @@ func (p *Parlia) backOffTime(snap *Snapshot, header *types.Header, val common.Ad
}
}

func (p *Parlia) BlockInterval() uint64 {
return p.config.Period
}

func (p *Parlia) NextProposalBlock(chain consensus.ChainHeaderReader, header *types.Header, proposer common.Address) (uint64, uint64, error) {
snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
return 0, 0, err
}

return snap.nextProposalBlock(proposer)
}

// chain context
type chainContext struct {
Chain consensus.ChainHeaderReader
Expand Down
31 changes: 31 additions & 0 deletions consensus/parlia/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,37 @@ func (s *Snapshot) inturnValidator() common.Address {
return validators[offset]
}

func (s *Snapshot) nexValidatorsChangeBlock() uint64 {
currentEpoch := s.Number - s.Number%s.config.Epoch
checkLen := s.minerHistoryCheckLen()
if s.Number%s.config.Epoch < checkLen {
return currentEpoch + checkLen
}
return currentEpoch + s.config.Epoch + checkLen
}

// nextProposalBlock returns the validator next proposal block.
func (s *Snapshot) nextProposalBlock(proposer common.Address) (uint64, uint64, error) {
validators := s.validators()
currentIndex := int(s.Number / uint64(s.TurnLength) % uint64(len(validators)))
expectIndex := s.indexOfVal(proposer)
if expectIndex < 0 {
return 0, 0, errors.New("proposer not in validator set")
}
startBlock := s.Number + uint64(((expectIndex+len(validators)-currentIndex)%len(validators))*int(s.TurnLength))
startBlock = startBlock - startBlock%uint64(s.TurnLength)
endBlock := startBlock + uint64(s.TurnLength) - 1

changeValidatorsBlock := s.nexValidatorsChangeBlock()
if startBlock >= changeValidatorsBlock {
return 0, 0, errors.New("next proposal block is out of current epoch")
}
if endBlock >= changeValidatorsBlock {
endBlock = changeValidatorsBlock
}
return startBlock, endBlock, nil
}

func (s *Snapshot) enoughDistance(validator common.Address, header *types.Header) bool {
idx := s.indexOfVal(validator)
if idx < 0 {
Expand Down
3 changes: 3 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,9 @@ func (s *Ethereum) setupDiscovery() error {
// Stop implements node.Lifecycle, terminating all internal goroutines used by the
// Ethereum protocol.
func (s *Ethereum) Stop() error {
if s.miner.Mining() {
s.miner.TryWaitProposalDoneWhenStopping()
}
// Stop all the peer-related stuff first.
s.discmix.Close()
s.handler.Stop()
Expand Down
4 changes: 4 additions & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ func (miner *Miner) InTurn() bool {
return miner.worker.inTurn()
}

func (miner *Miner) TryWaitProposalDoneWhenStopping() {
miner.worker.tryWaitProposalDoneWhenStopping()
}

// Pending returns the currently pending block and associated receipts, logs
// and statedb. The returned values can be nil in case the pending block is
// not initialized.
Expand Down
21 changes: 13 additions & 8 deletions miner/minerconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ import (

// Config is the configuration parameters of mining.
type Config struct {
Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards
ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner
DelayLeftOver time.Duration // Time reserved to finalize a block(calculate root, distribute income...)
GasFloor uint64 // Target gas floor for mined blocks.
GasCeil uint64 // Target gas ceiling for mined blocks.
GasPrice *big.Int // Minimum gas price for mining a transaction
Recommit time.Duration // The time interval for miner to re-create mining work.
VoteEnable bool // Whether to vote when mining
Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards
ExtraData hexutil.Bytes `toml:",omitempty"` // Block extra data set by the miner
DelayLeftOver time.Duration // Time reserved to finalize a block(calculate root, distribute income...)
GasFloor uint64 // Target gas floor for mined blocks.
GasCeil uint64 // Target gas ceiling for mined blocks.
GasPrice *big.Int // Minimum gas price for mining a transaction
Recommit time.Duration // The time interval for miner to re-create mining work.
VoteEnable bool // Whether to vote when mining
MaxWaitProposalInSecs uint64 // The maximum time to wait for the proposal to be done, it's aimed to prevent validator being slashed when restarting

DisableVoteAttestation bool // Whether to skip assembling vote attestation

Expand All @@ -54,6 +55,10 @@ var DefaultConfig = Config{
Recommit: 3 * time.Second,
DelayLeftOver: 50 * time.Millisecond,

// The default value is set to 30 seconds.
// Because the avg restart time in mainnet is around 30s, so the node try to wait for the next multi-proposals to be done.
MaxWaitProposalInSecs: 30,

Mev: DefaultMevConfig,
}

Expand Down
33 changes: 33 additions & 0 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,39 @@ func (w *worker) getSealingBlock(params *generateParams) *newPayloadResult {
}
}

func (w *worker) tryWaitProposalDoneWhenStopping() {
posa, ok := w.engine.(consensus.PoSA)
// if the consensus is not PoSA, just skip waiting
if !ok {
return
}

currentHeader := w.chain.CurrentBlock()
currentBlock := currentHeader.Number.Uint64()
startBlock, endBlock, err := posa.NextProposalBlock(w.chain, currentHeader, w.coinbase)
if err != nil {
log.Warn("Failed to get next proposal block, skip waiting", "err", err)
return
}

log.Info("Checking miner's next proposal block", "current", currentBlock,
"proposalStart", startBlock, "proposalEnd", endBlock, "maxWait", w.config.MaxWaitProposalInSecs)
if endBlock <= currentBlock {
log.Warn("next proposal end block has passed, ignore")
return
}
if startBlock > currentBlock && (startBlock-currentBlock)*posa.BlockInterval() > w.config.MaxWaitProposalInSecs {
log.Warn("the next proposal start block is too far, just skip waiting")
return
}

// wait one more block for safety
waitSecs := (endBlock - currentBlock + 1) * posa.BlockInterval()
log.Info("The miner will propose in later, waiting for the proposal to be done",
"currentBlock", currentBlock, "nextProposalStart", startBlock, "nextProposalEnd", endBlock, "waitTime", waitSecs)
time.Sleep(time.Duration(waitSecs) * time.Second)
}

// copyReceipts makes a deep copy of the given receipts.
func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
result := make([]*types.Receipt, len(receipts))
Expand Down
Loading