Skip to content
Open
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
6 changes: 4 additions & 2 deletions epochStart/metachain/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import (
"github.com/multiversx/mx-chain-core-go/display"
"github.com/multiversx/mx-chain-core-go/hashing"
"github.com/multiversx/mx-chain-core-go/marshal"
"github.com/multiversx/mx-chain-logger-go"
"github.com/multiversx/mx-chain-go/common"
"github.com/multiversx/mx-chain-go/config"
"github.com/multiversx/mx-chain-go/dataRetriever"
"github.com/multiversx/mx-chain-go/epochStart"
"github.com/multiversx/mx-chain-go/process"
"github.com/multiversx/mx-chain-go/storage"
"github.com/multiversx/mx-chain-logger-go"
)

var log = logger.GetOrCreate("epochStart/metachain")
Expand Down Expand Up @@ -209,9 +209,11 @@ func (t *trigger) Update(round uint64, nonce uint64) {
}

isZeroEpochEdgeCase := nonce < minimumNonceToStartEpoch
epochStartNonce := t.epochStartMeta.GetNonce()
hasMinBlocksInEpoch := nonce >= epochStartNonce+minimumNonceToStartEpoch
isNormalEpochStart := t.currentRound > t.currEpochStartRound+t.roundsPerEpoch
isWithEarlyEndOfEpoch := t.currentRound >= t.nextEpochStartRound
shouldTriggerEpochStart := (isNormalEpochStart || isWithEarlyEndOfEpoch) && !isZeroEpochEdgeCase
shouldTriggerEpochStart := (isNormalEpochStart || isWithEarlyEndOfEpoch) && !isZeroEpochEdgeCase && hasMinBlocksInEpoch
if shouldTriggerEpochStart {
t.epoch += 1
t.isEpochStart = true
Expand Down
79 changes: 79 additions & 0 deletions epochStart/metachain/trigger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,82 @@ func TestTrigger_RevertBehindEpochStartBlock(t *testing.T) {
ret = epochStartTrigger.IsEpochStart()
assert.False(t, ret)
}

func TestTrigger_UpdateShouldNotStartEpochWithLessThanMinimumBlocks(t *testing.T) {
t.Parallel()

epoch := uint32(0)
arguments := createMockEpochStartTriggerArguments()
arguments.Settings.RoundsPerEpoch = 2
arguments.Settings.MinRoundsBetweenEpochs = 1
arguments.Epoch = epoch
epochStartTrigger, err := NewEpochStartTrigger(arguments)
require.Nil(t, err)

epochStartNonce := uint64(100)
epochStartRound := uint64(50)

// simulate an epoch start block already processed
epochStartTrigger.SetProcessed(&block.MetaBlock{
Nonce: epochStartNonce,
Round: epochStartRound,
Epoch: epoch,
EpochStart: block.EpochStart{
LastFinalizedHeaders: []block.EpochStartShardData{{RootHash: []byte("root")}},
},
}, nil)
require.False(t, epochStartTrigger.IsEpochStart())
require.Equal(t, epoch, epochStartTrigger.Epoch())

// round condition is met but nonce is only epochStartNonce+1 (1 block in epoch)
// this should NOT trigger a new epoch because minimumBlocksPerEpoch = 2
nextRound := epochStartRound + uint64(arguments.Settings.RoundsPerEpoch) + 1
epochStartTrigger.Update(nextRound, epochStartNonce+1)
assert.False(t, epochStartTrigger.IsEpochStart(),
"epoch should not start with only 1 block in the current epoch")
assert.Equal(t, epoch, epochStartTrigger.Epoch())

// now with nonce = epochStartNonce+4 (4 blocks in epoch), it should trigger
epochStartTrigger.Update(nextRound+1, epochStartNonce+4)
assert.True(t, epochStartTrigger.IsEpochStart(),
"epoch should start once minimum blocks per epoch is reached")
assert.Equal(t, epoch+1, epochStartTrigger.Epoch())
}

func TestTrigger_ForceEpochStartShouldRespectMinimumBlocks(t *testing.T) {
t.Parallel()

epoch := uint32(0)
arguments := createMockEpochStartTriggerArguments()
arguments.Settings.RoundsPerEpoch = 200
arguments.Settings.MinRoundsBetweenEpochs = 20
arguments.Epoch = epoch
epochStartTrigger, err := NewEpochStartTrigger(arguments)
require.Nil(t, err)

epochStartNonce := uint64(500)
epochStartRound := uint64(1000)

// simulate an epoch start block already processed
epochStartTrigger.SetProcessed(&block.MetaBlock{
Nonce: epochStartNonce,
Round: epochStartRound,
Epoch: epoch,
EpochStart: block.EpochStart{
LastFinalizedHeaders: []block.EpochStartShardData{{RootHash: []byte("root")}},
},
}, nil)

// force epoch start at round 1025
epochStartTrigger.ForceEpochStart(epochStartRound + 25)

// round condition met via force, but only 1 block since epoch start
epochStartTrigger.Update(epochStartRound+25, epochStartNonce+1)
assert.False(t, epochStartTrigger.IsEpochStart(),
"forced epoch should not start with only 1 block in the current epoch")

// with 4 blocks, the forced epoch start should proceed
epochStartTrigger.Update(epochStartRound+26, epochStartNonce+4)
assert.True(t, epochStartTrigger.IsEpochStart(),
"forced epoch should start once minimum blocks per epoch is reached")
}
16 changes: 0 additions & 16 deletions process/block/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -832,22 +832,6 @@ func DisplayHeader(
return displayHeader(headerHandler, headerProof)
}

// DetectStaleSelfNotarizedHeaders -
func (mp *metaProcessor) DetectStaleSelfNotarizedHeaders() bool {
return mp.detectStaleSelfNotarizedHeaders()
}

// SetSelfNotarizedHeadersStale -
func (mp *metaProcessor) SetSelfNotarizedHeadersStale(stale bool) {
mp.selfNotarizedHeadersStale = stale
mp.selfNotarizedHeadersStaleOnce.Do(func() {})
}

// GetSelfNotarizedHeadersStale -
func (mp *metaProcessor) GetSelfNotarizedHeadersStale() bool {
return mp.selfNotarizedHeadersStale
}

// VerifyShardDataAgainstHeaders -
func (mp *metaProcessor) VerifyShardDataAgainstHeaders(metaHdr *block.MetaBlock) error {
return mp.verifyShardDataAgainstHeaders(metaHdr)
Expand Down
90 changes: 35 additions & 55 deletions process/block/metablock.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,17 @@ var _ process.BlockProcessor = (*metaProcessor)(nil)
// metaProcessor implements metaProcessor interface, and actually it tries to execute block
type metaProcessor struct {
*baseProcessor
scToProtocol process.SmartContractToProtocolHandler
epochStartDataCreator process.EpochStartDataCreator
epochEconomics process.EndOfEpochEconomics
epochRewardsCreator process.RewardsCreator
validatorInfoCreator process.EpochStartValidatorInfoCreator
epochSystemSCProcessor process.EpochStartSystemSCProcessor
pendingMiniBlocksHandler process.PendingMiniBlocksHandler
validatorStatisticsProcessor process.ValidatorStatisticsProcessor
shardsHeadersNonce *sync.Map
shardBlockFinality uint32
headersCounter *headersCounter
selfNotarizedHeadersStale bool
selfNotarizedHeadersStaleOnce sync.Once
scToProtocol process.SmartContractToProtocolHandler
epochStartDataCreator process.EpochStartDataCreator
epochEconomics process.EndOfEpochEconomics
epochRewardsCreator process.RewardsCreator
validatorInfoCreator process.EpochStartValidatorInfoCreator
epochSystemSCProcessor process.EpochStartSystemSCProcessor
pendingMiniBlocksHandler process.PendingMiniBlocksHandler
validatorStatisticsProcessor process.ValidatorStatisticsProcessor
shardsHeadersNonce *sync.Map
shardBlockFinality uint32
headersCounter *headersCounter
}

// NewMetaProcessor creates a new metaProcessor object
Expand Down Expand Up @@ -190,27 +188,6 @@ func NewMetaProcessor(arguments ArgMetaProcessor) (*metaProcessor, error) {
return &mp, nil
}

// detectStaleSelfNotarizedHeaders returns true when bootstrap data has stale self-notarized
// headers (nonce 0) while cross-notarized headers have progressed past genesis.
func (mp *metaProcessor) detectStaleSelfNotarizedHeaders() bool {
for shardID := uint32(0); shardID < mp.shardCoordinator.NumberOfShards(); shardID++ {
crossNotarized, _, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardID)
if err != nil || check.IfNil(crossNotarized) || crossNotarized.GetNonce() == 0 {
continue
}

selfNotarized, _, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardID)
if err != nil || check.IfNil(selfNotarized) || selfNotarized.GetNonce() == 0 {
log.Debug("detected stale self-notarized headers after bootstrap",
"shardID", shardID,
"crossNotarizedNonce", crossNotarized.GetNonce())
return true
}
}

return false
}

func (mp *metaProcessor) isRewardsV2Enabled(headerHandler data.HeaderHandler) bool {
return mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.StakingV2Flag, headerHandler.GetEpoch())
}
Expand Down Expand Up @@ -1371,6 +1348,8 @@ func (mp *metaProcessor) CommitBlock(
mp.blockTracker.AddSelfNotarizedHeader(shardID, lastSelfNotarizedHeader, lastSelfNotarizedHeaderHash)
}

mp.completeMissingSelfNotarizedHeaders(header)

go mp.historyRepo.OnNotarizedBlocks(mp.shardCoordinator.SelfId(), []data.HeaderHandler{currentHeader}, [][]byte{currentHeaderHash})

log.Debug("highest final meta block",
Expand Down Expand Up @@ -1458,10 +1437,6 @@ func (mp *metaProcessor) CommitBlock(

mp.blockProcessingCutoffHandler.HandlePauseCutoff(header)

if mp.selfNotarizedHeadersStale {
mp.selfNotarizedHeadersStale = mp.detectStaleSelfNotarizedHeaders()
}

return nil
}

Expand Down Expand Up @@ -1668,7 +1643,10 @@ func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard(
mp.store,
)
if errGet != nil {
log.Trace("getLastSelfNotarizedHeaderByShard.GetMetaHeader", "error", errGet.Error())
log.Warn("getLastSelfNotarizedHeaderByShard: could not get referenced meta header, self notarized may not be updated",
"shardID", shardID,
"metaHash", metaHash,
"error", errGet.Error())
continue
}

Expand All @@ -1693,6 +1671,16 @@ func (mp *metaProcessor) getLastSelfNotarizedHeaderByShard(
return lastNotarizedMetaHeader, lastNotarizedMetaHeaderHash
}

func (mp *metaProcessor) completeMissingSelfNotarizedHeaders(currentHeader *block.MetaBlock) {
process.CompleteMissingSelfNotarizedHeaders(
currentHeader.GetPrevHash(),
mp.shardCoordinator.NumberOfShards(),
mp.blockTracker,
mp.marshalizer,
mp.store,
)
}

// getRewardsTxs must be called before method commitEpoch start because when commit is done rewards txs are removed from pool and saved in storage
func (mp *metaProcessor) getRewardsTxs(header *block.MetaBlock, body *block.Body) (rewardsTx map[string]data.TransactionHandler) {
if !mp.outportHandler.HasDrivers() {
Expand Down Expand Up @@ -1910,10 +1898,6 @@ func (mp *metaProcessor) checkShardHeadersValidity(metaHdr *block.MetaBlock) (ma
}

func (mp *metaProcessor) verifyShardDataAgainstHeaders(metaHdr *block.MetaBlock) error {
mp.selfNotarizedHeadersStaleOnce.Do(func() {
mp.selfNotarizedHeadersStale = mp.detectStaleSelfNotarizedHeaders()
})

mp.hdrsForCurrBlock.mutHdrsForBlock.Lock()
defer mp.hdrsForCurrBlock.mutHdrsForBlock.Unlock()

Expand Down Expand Up @@ -1943,15 +1927,11 @@ func (mp *metaProcessor) verifyShardDataAgainstHeaders(metaHdr *block.MetaBlock)
expected := mp.buildShardDataFromHeader(shardHdr, shardData.HeaderHash)
expected.NumPendingMiniBlocks = uint32(len(mp.pendingMiniBlocksHandler.GetPendingMiniBlocks(expected.ShardID)))

if mp.selfNotarizedHeadersStale {
expected.LastIncludedMetaNonce = shardData.LastIncludedMetaNonce
} else {
lastSelfNotarizedHeader, _, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardHdr.GetShardID())
if err != nil {
return err
}
expected.LastIncludedMetaNonce = lastSelfNotarizedHeader.GetNonce()
lastSelfNotarizedHeader, _, err := mp.blockTracker.GetLastSelfNotarizedHeader(shardHdr.GetShardID())
if err != nil {
return err
}
expected.LastIncludedMetaNonce = lastSelfNotarizedHeader.GetNonce()

if !expected.Equal(&shardData) {
log.Debug("shard data mismatch",
Expand Down Expand Up @@ -2270,14 +2250,14 @@ func (mp *metaProcessor) computeExistingAndRequestMissingShardHeaders(metaBlock

mp.requestProofIfNeeded(shardData.HeaderHash, hdr)

if common.IsEpochChangeBlockForFlagActivation(hdr, mp.enableEpochsHandler, common.AndromedaFlag) {
continue
}

if hdr.GetNonce() > mp.hdrsForCurrBlock.highestHdrNonce[shardData.ShardID] {
mp.hdrsForCurrBlock.highestHdrNonce[shardData.ShardID] = hdr.GetNonce()
}

if common.IsEpochChangeBlockForFlagActivation(hdr, mp.enableEpochsHandler, common.AndromedaFlag) {
continue
}

mp.updateLastNotarizedBlockForShard(hdr, shardData.HeaderHash)
}

Expand Down
Loading
Loading