Skip to content
Draft
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
10 changes: 10 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,3 +640,13 @@ func GetFeePayer(tx data.TransactionHandler) []byte {

return tx.GetSndAddr()
}

// IsContendedHeader returns true if rounds were skipped between the parent and the header, so a
// competing proof could exist at the header's nonce in a skipped round
func IsContendedHeader(header data.HeaderHandler, parentHeader data.HeaderHandler) bool {
if check.IfNil(header) || check.IfNil(parentHeader) {
return false
}

return header.GetRound() > parentHeader.GetRound()+1
}
16 changes: 16 additions & 0 deletions common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1372,3 +1372,19 @@ func TestPrepareUnexecutableTxHashesKey(t *testing.T) {
hash := []byte("hash")
require.Equal(t, []byte("unexecutablehash"), common.PrepareUnexecutableTxHashesKey(hash))
}

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

parent := &testscommon.HeaderHandlerStub{RoundField: 1}

require.False(t, common.IsContendedHeader(nil, parent))
require.False(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 5}, nil))

// consecutive round: no skipped round to hide a competitor in
require.False(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 2}, parent))

// at least one skipped round: a competing proof could exist there
require.True(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 3}, parent))
require.True(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 10}, parent))
}
9 changes: 9 additions & 0 deletions factory/mock/blockTrackerStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BlockTrackerStub struct {
ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool
ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64)
IsHeaderQuarantinedCalled func(hash []byte) bool
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
}

// AddTrackedHeader -
Expand Down Expand Up @@ -327,3 +328,11 @@ func (bts *BlockTrackerStub) Close() error {
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
return bts == nil
}

// IsSettledCrossHeader -
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
if bts.IsSettledCrossHeaderCalled != nil {
return bts.IsSettledCrossHeaderCalled(header, headerHash)
}
return false
}
9 changes: 9 additions & 0 deletions integrationTests/mock/blockTrackerStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type BlockTrackerStub struct {
ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool
ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64)
IsHeaderQuarantinedCalled func(hash []byte) bool
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
}

// AddTrackedHeader -
Expand Down Expand Up @@ -327,3 +328,11 @@ func (bts *BlockTrackerStub) Close() error {
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
return bts == nil
}

// IsSettledCrossHeader -
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
if bts.IsSettledCrossHeaderCalled != nil {
return bts.IsSettledCrossHeaderCalled(header, headerHash)
}
return false
}
9 changes: 9 additions & 0 deletions node/mock/blockTrackerStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type BlockTrackerStub struct {
CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error
CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error
IsHeaderQuarantinedCalled func(hash []byte) bool
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
}

// CheckProofAgainstFinal -
Expand Down Expand Up @@ -325,3 +326,11 @@ func (bts *BlockTrackerStub) Close() error {
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
return bts == nil
}

// IsSettledCrossHeader -
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
if bts.IsSettledCrossHeaderCalled != nil {
return bts.IsSettledCrossHeaderCalled(header, headerHash)
}
return false
}
35 changes: 35 additions & 0 deletions process/block/baseProcess.go
Original file line number Diff line number Diff line change
Expand Up @@ -4382,3 +4382,38 @@ func (bp *baseProcessor) pruneTrieForHeadersUnprotected(

return nil
}

// isContendedUnsettledCrossHeader applies the R-CROSS referencing gate: a cross-shard header that
// skipped a round after its parent is not includable until a proofed child settles it
func (bp *baseProcessor) isContendedUnsettledCrossHeader(header data.HeaderHandler, parentHeader data.HeaderHandler, headerHash []byte) bool {
if !bp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) {
return false
}
if !common.IsContendedHeader(header, parentHeader) {
return false
}

return !bp.blockTracker.IsSettledCrossHeader(header, headerHash)
}

// checkNotContendedUnsettled errors when a referenced cross-shard header is contended and not yet
// settled; the header hash is computed only on the contended path
func (bp *baseProcessor) checkNotContendedUnsettled(header data.HeaderHandler, parentHeader data.HeaderHandler) error {
if !bp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) {
return nil
}
if !common.IsContendedHeader(header, parentHeader) {
return nil
}

headerHash, err := bp.getHeaderHash(header)
if err != nil {
return err
}

if !bp.blockTracker.IsSettledCrossHeader(header, headerHash) {
return fmt.Errorf("%w with hash %x", errIncludedContendedUnsettledHeader, headerHash)
}

return nil
}
2 changes: 2 additions & 0 deletions process/block/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ var errNilPreviousHeader = errors.New("nil previous header")
var errInvalidMiniBlocks = errors.New("invalid mini blocks")

var errIncludedQuarantinedHeader = errors.New("included quarantined header")

var errIncludedContendedUnsettledHeader = errors.New("included contended header not yet settled")
5 changes: 5 additions & 0 deletions process/block/metablock.go
Original file line number Diff line number Diff line change
Expand Up @@ -2226,6 +2226,11 @@ func (mp *metaProcessor) checkShardHeadersValidity(metaHdr data.MetaHeaderHandle
if err != nil {
return nil, fmt.Errorf("%w : checkShardHeadersValidity -> isHdrConstructionValid", err)
}

err = mp.checkNotContendedUnsettled(shardHdr, lastCrossNotarizedHeader[shardID])
if err != nil {
return nil, fmt.Errorf("%w : checkShardHeadersValidity", err)
}
}

lastCrossNotarizedHeader[shardID] = shardHdr
Expand Down
4 changes: 4 additions & 0 deletions process/block/metablockProposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,10 @@ func (mp *metaProcessor) checkHeadersSequenceCorrectness(hdrsForShard []ShardHea
return fmt.Errorf("%w with hash %x", errIncludedQuarantinedHeader, shardHdrInfo.Hash)
}

if mp.isContendedUnsettledCrossHeader(shardHdrInfo.Header, lastNotarizedHeaderInfoForShard.Header, shardHdrInfo.Hash) {
return fmt.Errorf("%w with hash %x", errIncludedContendedUnsettledHeader, shardHdrInfo.Hash)
}

err = mp.headerValidator.IsHeaderConstructionValid(shardHdrInfo.Header, lastNotarizedHeaderInfoForShard.Header)
if err != nil {
return err
Expand Down
118 changes: 113 additions & 5 deletions process/block/metablockProposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/multiversx/mx-chain-go/process/mock"
"github.com/multiversx/mx-chain-go/testscommon"
dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever"
"github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock"
"github.com/multiversx/mx-chain-go/testscommon/mbSelection"
"github.com/multiversx/mx-chain-go/testscommon/pool"
"github.com/multiversx/mx-chain-go/testscommon/processMocks"
Expand Down Expand Up @@ -1788,7 +1789,8 @@ func Test_checkShardHeadersValidityAndFinalityProposal(t *testing.T) {
dataPoolMock.SetHeadersPool(headersPoolMock)

mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
"blockTracker": &mock.BlockTrackerMock{
GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) {
return &testscommon.HeaderHandlerStub{}, nil, nil
Expand Down Expand Up @@ -1905,7 +1907,8 @@ func Test_checkShardHeadersValidityAndFinalityProposal(t *testing.T) {
}

mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
"blockTracker": &mock.BlockTrackerMock{
GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) {
return &testscommon.HeaderHandlerStub{}, nil, nil
Expand Down Expand Up @@ -1950,7 +1953,8 @@ func Test_checkShardHeadersValidityAndFinalityProposal(t *testing.T) {
dataPoolMock.SetHeadersPool(headersPoolMock)

mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
"blockTracker": &mock.BlockTrackerMock{
GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) {
return &testscommon.HeaderHandlerStub{}, nil, nil
Expand Down Expand Up @@ -3583,7 +3587,8 @@ func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) {
return expectedErr
},
},
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
})
require.Nil(t, err)

Expand Down Expand Up @@ -3622,6 +3627,108 @@ func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) {
require.ErrorContains(t, err, "included quarantined header")
})

t.Run("should return error for contended unsettled header", func(t *testing.T) {
t.Parallel()

contendedHash := []byte("contended hash")
mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"headerValidator": &processMocks.HeaderValidatorMock{
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
return nil
},
},
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
return flag == common.SupernovaFlag
},
},
"blockTracker": &integrationTestsMock.BlockTrackerStub{
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
return false
},
},
})
require.Nil(t, err)

err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
{
// rounds 2-4 skipped after the last notarized shard header
Header: &block.Header{Nonce: 2, Round: 5},
Hash: contendedHash,
},
}, blproc.ShardHeaderInfo{
Header: &block.Header{Nonce: 1, Round: 1},
})
require.ErrorContains(t, err, "included contended header not yet settled")
})

t.Run("should work for contended settled header", func(t *testing.T) {
t.Parallel()

mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"headerValidator": &processMocks.HeaderValidatorMock{
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
return nil
},
},
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
return flag == common.SupernovaFlag
},
},
"blockTracker": &integrationTestsMock.BlockTrackerStub{
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
return true
},
},
})
require.Nil(t, err)

err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
{
Header: &block.Header{Nonce: 2, Round: 5},
Hash: []byte("contended settled hash"),
},
}, blproc.ShardHeaderInfo{
Header: &block.Header{Nonce: 1, Round: 1},
})
require.Nil(t, err)
})

t.Run("should work for non-contended header without settlement lookup", func(t *testing.T) {
t.Parallel()

mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
"headerValidator": &processMocks.HeaderValidatorMock{
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
return nil
},
},
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
return flag == common.SupernovaFlag
},
},
"blockTracker": &integrationTestsMock.BlockTrackerStub{
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
require.Fail(t, "settlement must not be checked on the clean path")
return false
},
},
})
require.Nil(t, err)

err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
{
Header: &block.Header{Nonce: 2, Round: 2},
Hash: []byte("clean hash"),
},
}, blproc.ShardHeaderInfo{
Header: &block.Header{Nonce: 1, Round: 1},
})
require.Nil(t, err)
})

t.Run("should work", func(t *testing.T) {
t.Parallel()

Expand All @@ -3636,7 +3743,8 @@ func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) {
return nil
},
},
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
})
require.Nil(t, err)

Expand Down
Loading
Loading