Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,14 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [

// Prepare implements consensus.Engine, initializing the difficulty field of a
// header to conform to the beacon protocol. The changes are done inline.
func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header, waitOnPrepare bool) error {
func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
// Transition isn't triggered yet, use the legacy rules for preparation.
reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1)
if err != nil {
return err
}
if !reached {
return beacon.ethone.Prepare(chain, header, waitOnPrepare)
return beacon.ethone.Prepare(chain, header)
}
header.Difficulty = beaconDifficulty
return nil
Expand Down
218 changes: 125 additions & 93 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,16 @@
inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory
inmemorySignatures = 4096 // Number of recent block signatures to keep in memory
veblopBlockTimeout = time.Second * 8 // Timeout for new span check. DO NOT CHANGE THIS VALUE.
minBlockBuildTime = 1 * time.Second // Minimum remaining time before extending the block deadline to avoid empty blocks
// minBlockBuildTime is the minimum remaining time before Prepare() extends
// the block deadline to avoid producing empty blocks. If time.Until(target)
// is less than this value, the target timestamp is pushed forward by one
// blockTime period.
//
// Abort-recovery rebuilds from pipelined SRC are exempt from this push. By the
// time speculative execution is discarded, most of the slot may already be
// gone; moving the header to the next slot would create avoidable 3-second
// blocks on 2-second devnets.
minBlockBuildTime = 1 * time.Second
)

// Bor protocol constants.
Expand Down Expand Up @@ -1009,7 +1018,7 @@

// Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top.
func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, waitOnPrepare bool) error {
func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {

Check failure on line 1021 in consensus/bor/bor.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 47 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZ2V1BT92ziWcsgoKHq_&open=AZ2V1BT92ziWcsgoKHq_&pullRequest=2180
// If the block isn't a checkpoint, cast a random vote (good enough for now)
header.Coinbase = common.Address{}
header.Nonce = types.BlockNonce{}
Expand Down Expand Up @@ -1112,8 +1121,6 @@
return fmt.Errorf("the floor of custom mining block time (%v) is less than the consensus block time: %v < %v", c.blockTime, c.blockTime.Seconds(), c.config.CalculatePeriod(number))
}

var delay time.Duration

if c.blockTime > 0 && c.config.IsRio(header.Number) {
// Only enable custom block time for Rio and later

Expand All @@ -1131,10 +1138,8 @@
actualNewBlockTime := parentActualBlockTime.Add(c.blockTime)
header.Time = uint64(actualNewBlockTime.Unix())
header.ActualTime = actualNewBlockTime
delay = time.Until(parentActualBlockTime)
} else {
header.Time = parent.Time + CalcProducerDelay(number, succession, c.config)
delay = time.Until(time.Unix(int64(parent.Time), 0))
}

now := time.Now()
Expand All @@ -1145,29 +1150,17 @@
// Ensure minimum build time so the block has enough time to include transactions.
// The interrupt timer reserves 500ms for state root computation, so without
// sufficient remaining time the block would end up empty.
if time.Until(header.GetActualTime()) < minBlockBuildTime {
//
// Abort-recovery rebuilds are different: speculative execution has already
// spent most of the slot, so pushing them again would create an avoidable
// extra block-time gap. Those late rebuilds should keep their original slot.
if !header.AbortRecovery && time.Until(header.GetActualTime()) < minBlockBuildTime {
header.Time = uint64(now.Add(blockTime).Unix())
if c.blockTime > 0 && c.config.IsRio(header.Number) {
header.ActualTime = now.Add(blockTime)
}
}

// Wait before start the block production if needed (previously this wait was on Seal)
if c.config.IsGiugliano(header.Number) && waitOnPrepare {
var successionNumber int
// if signer is not empty (RPC nodes have empty signer)
if currentSigner.signer != (common.Address{}) {
var err error
successionNumber, err = snap.GetSignerSuccessionNumber(currentSigner.signer)
if err != nil {
return err
}
if successionNumber == 0 {
<-time.After(delay)
}
}
}

return nil
}

Expand All @@ -1193,7 +1186,7 @@
// check and commit span
if !c.config.IsRio(header.Number) {
if err := c.checkAndCommitSpan(wrappedState, header, cx); err != nil {
log.Error("Error while committing span", "error", err)

Check failure on line 1189 in consensus/bor/bor.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Error while committing span" 3 times.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZ1JxLOFMy6llO4qwxL2&open=AZ1JxLOFMy6llO4qwxL2&pullRequest=2180
return nil
}
}
Expand All @@ -1202,7 +1195,7 @@
// commit states
stateSyncData, err = c.CommitStates(wrappedState, header, cx)
if err != nil {
log.Error("Error while committing states", "error", err)

Check failure on line 1198 in consensus/bor/bor.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Error while committing states" 3 times.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZ1JxLOFMy6llO4qwxL4&open=AZ1JxLOFMy6llO4qwxL4&pullRequest=2180
return nil
}
}
Expand All @@ -1215,7 +1208,7 @@
// the wrapped state here as it may have a hooked state db instance which can help
// in tracing if it's enabled.
if err = c.changeContractCodeIfNeeded(headerNumber, wrappedState); err != nil {
log.Error("Error changing contract code", "error", err)

Check failure on line 1211 in consensus/bor/bor.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Error changing contract code" 3 times.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZ1JxLOFMy6llO4qwxL3&open=AZ1JxLOFMy6llO4qwxL3&pullRequest=2180
return nil
}

Expand Down Expand Up @@ -1361,25 +1354,9 @@
return nil, nil, 0, err
}

// No block rewards in PoA, so the state remains as it is
start := time.Now()

// No block rewards in PoA, so the state remains as it is.
// Under delayed SRC, header.Root stores the parent block's actual state root;
// the goroutine in BlockChain.spawnSRCGoroutine handles this block's root.
if c.chainConfig.Bor != nil && c.chainConfig.Bor.IsDelayedSRC(header.Number) {
dsrcReader, ok := chain.(core.DelayedSRCReader)
if !ok {
return nil, nil, 0, fmt.Errorf("chain does not implement DelayedSRCReader")
}
parentRoot := dsrcReader.GetPostStateRoot(header.ParentHash)
if parentRoot == (common.Hash{}) {
return nil, nil, 0, fmt.Errorf("delayed state root unavailable for parent %s", header.ParentHash)
}
header.Root = parentRoot
} else {
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}

header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
commitTime := time.Since(start)

// Uncles are dropped
Expand All @@ -1404,6 +1381,81 @@
return block, receipts, commitTime, nil
}

// FinalizeForPipeline runs the same post-transaction state modifications as
// FinalizeAndAssemble (state sync, span commits, contract code changes) but
// does NOT compute IntermediateRoot or assemble the block. It returns the
// stateSyncData so the caller can pass it to AssembleBlock later after the
// background SRC goroutine has computed the state root.
//
// This is the pipelined SRC equivalent of the first half of FinalizeAndAssemble.
func (c *Bor) FinalizeForPipeline(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, body *types.Body, receipts []*types.Receipt) ([]*types.StateSyncData, error) {
headerNumber := header.Number.Uint64()
if body.Withdrawals != nil || header.WithdrawalsHash != nil {
return nil, consensus.ErrUnexpectedWithdrawals
}
if header.RequestsHash != nil {
return nil, consensus.ErrUnexpectedRequests
}

var (
stateSyncData []*types.StateSyncData
err error
)

if IsSprintStart(headerNumber, c.config.CalculateSprint(headerNumber)) {
cx := statefull.ChainContext{Chain: chain, Bor: c}

if !c.config.IsRio(header.Number) {
if err = c.checkAndCommitSpan(statedb, header, cx); err != nil {
log.Error("Error while committing span", "error", err)
return nil, err
}
}

if c.HeimdallClient != nil {
stateSyncData, err = c.CommitStates(statedb, header, cx)
if err != nil {
log.Error("Error while committing states", "error", err)
return nil, err
}
}
}

if err = c.changeContractCodeIfNeeded(headerNumber, statedb); err != nil {
log.Error("Error changing contract code", "error", err)
return nil, err
}

return stateSyncData, nil
}

// AssembleBlock constructs the final block from a pre-computed state root,
// without calling IntermediateRoot. This is used by pipelined SRC where the
// state root is computed by a background goroutine.
//
// stateSyncData is the state sync data collected during Finalize(). If non-nil
// and the Madhugiri fork is active, a StateSyncTx is appended to the body.
func (c *Bor) AssembleBlock(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, body *types.Body, receipts []*types.Receipt, stateRoot common.Hash, stateSyncData []*types.StateSyncData) (*types.Block, []*types.Receipt, error) {
headerNumber := header.Number.Uint64()

header.Root = stateRoot
header.UncleHash = types.CalcUncleHash(nil)

if len(stateSyncData) > 0 && c.config != nil && c.config.IsMadhugiri(big.NewInt(int64(headerNumber))) {
stateSyncTx := types.NewTx(&types.StateSyncTx{
StateSyncData: stateSyncData,
})
body.Transactions = append(body.Transactions, stateSyncTx)
receipts = insertStateSyncTransactionAndCalculateReceipt(stateSyncTx, header, body, statedb, receipts)
} else {
bc := chain.(core.BorStateSyncer)
bc.SetStateSync(stateSyncData)
}

block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
return block, receipts, nil
}

// Authorize injects a private key into the consensus engine to mint new blocks
// with.
func (c *Bor) Authorize(currentSigner common.Address, signFn SignerFn) {
Expand Down Expand Up @@ -1449,12 +1501,11 @@

var delay time.Duration

// Sweet, the protocol permits us to sign the block, wait for our time
if c.config.IsGiugliano(header.Number) && successionNumber == 0 {
delay = 0 // delay was moved to Prepare for giugliano and later
} else {
delay = time.Until(header.GetActualTime()) // Wait until we reach header time
}
// Sweet, the protocol permits us to sign the block, wait for our time.
// Sequential mining paths build the block body before the slot and rely on
// Seal to hold propagation until the target time. The pipeline paths may
// already have waited explicitly, in which case this is effectively zero.
delay = time.Until(header.GetActualTime())

// wiggle was already accounted for in header.Time, this is just for logging
wiggle := time.Duration(successionNumber) * time.Duration(c.config.CalculateBackupMultiplier(number)) * time.Second
Expand All @@ -1470,7 +1521,13 @@
}

// Wait until sealing is terminated or delay timeout.
log.Info("Waiting for slot to sign and propagate", "number", number, "hash", header.Hash(), "delay-in-sec", uint(delay), "delay", common.PrettyDuration(delay))
log.Info(
"Waiting for slot to sign and propagate",
"number", number,
"hash", header.Hash(),
"delay-ms", float64(delay)/float64(time.Millisecond),
"delay", common.PrettyDuration(delay),
)

go func() {
select {
Expand All @@ -1483,7 +1540,7 @@
"Sealing out-of-turn",
"number", number,
"hash", header.Hash,
"wiggle-in-sec", uint(wiggle),
"wiggle-ms", float64(wiggle)/float64(time.Millisecond),
"wiggle", common.PrettyDuration(wiggle),
"in-turn-signer", snap.ValidatorSet.GetProposer().Address.Hex(),
)
Expand Down Expand Up @@ -1597,38 +1654,22 @@
headerNumber := header.Number.Uint64()

tempState := state.Inner().Copy()
if c.chainConfig.Bor != nil && c.chainConfig.Bor.IsDelayedSRC(header.Number) {
// Under delayed SRC, skip ResetPrefetcher + StartPrefetcher.
// The full-node state is at root_{N-2} with a FlatDiff overlay
// approximating root_{N-1}. ResetPrefetcher clears that overlay,
// causing GetCurrentSpan to read stale root_{N-2} values — different
// from what the stateless node sees at root_{N-1}. The mismatch leads
// to different storage-slot access patterns, so the SRC goroutine
// captures the wrong trie nodes.
//
// StartPrefetcher is also unnecessary: the witness is built by the
// SRC goroutine, and tempState's reads are captured via
// CommitSnapshot + TouchAllAddresses below.
} else {
tempState.ResetPrefetcher()
tempState.StartPrefetcher("bor", state.Witness(), nil)
}
tempState.ResetPrefetcher()
tempState.StartPrefetcher("bor", state.Witness(), nil)

span, err := c.spanner.GetCurrentSpan(ctx, header.ParentHash, tempState)
if err != nil {
return err
}

if c.chainConfig.Bor != nil && c.chainConfig.Bor.IsDelayedSRC(header.Number) {
// Under delayed SRC, use CommitSnapshot instead of IntermediateRoot
// to capture all accesses without computing a trie root. Touch
// every address on the main state so they appear in the block's
// FlatDiff and the SRC goroutine includes their trie paths in
// the witness.
tempState.CommitSnapshot(false).TouchAllAddresses(state.Inner())
} else {
tempState.IntermediateRoot(false)
}
tempState.IntermediateRoot(false)

// Propagate addresses accessed during GetCurrentSpan back to the original
// state so they appear in the FlatDiff ReadSet. Without this, the pipelined
// SRC goroutine's witness won't capture their trie proof nodes (the copy's
// reads aren't tracked on the original), causing stateless execution to fail
// with missing trie nodes for the validator contract.
tempState.PropagateReadsTo(state.Inner())

if c.needToCommitSpan(span, headerNumber) {
return c.FetchAndCommitSpan(ctx, span.Id+1, state, header, chain)
Expand Down Expand Up @@ -1765,30 +1806,21 @@
if c.config.IsIndore(header.Number) {
// Fetch the LastStateId from contract via current state instance
tempState := state.Inner().Copy()
if c.chainConfig.Bor != nil && c.chainConfig.Bor.IsDelayedSRC(header.Number) {
// See comment in checkAndCommitSpan: under delayed SRC,
// skip ResetPrefetcher + StartPrefetcher to preserve the
// FlatDiff overlay and avoid stale root_{N-2} reads.
} else {
tempState.ResetPrefetcher()
tempState.StartPrefetcher("bor", state.Witness(), nil)
}
tempState.ResetPrefetcher()
tempState.StartPrefetcher("bor", state.Witness(), nil)

lastStateIDBig, err = c.GenesisContractsClient.LastStateId(tempState, number-1, header.ParentHash)
if err != nil {
return nil, err
}

if c.chainConfig.Bor != nil && c.chainConfig.Bor.IsDelayedSRC(header.Number) {
// Under delayed SRC, use CommitSnapshot instead of
// IntermediateRoot to capture all accesses without computing
// a trie root. Touch every address on the main state so they
// appear in the block's FlatDiff and the SRC goroutine
// includes their trie paths in the witness.
tempState.CommitSnapshot(false).TouchAllAddresses(state.Inner())
} else {
tempState.IntermediateRoot(false)
}
tempState.IntermediateRoot(false)

// Propagate addresses accessed during LastStateId back to the original
// state so they appear in the FlatDiff ReadSet. Without this, the
// pipelined SRC goroutine's witness won't capture their trie proof
// nodes, causing stateless execution to fail with missing trie nodes.
tempState.PropagateReadsTo(state.Inner())

stateSyncDelay := c.config.CalculateStateSyncDelay(number)
to = time.Unix(int64(header.Time-stateSyncDelay), 0)
Expand Down
Loading
Loading