Skip to content

Commit cd57dd1

Browse files
authored
Merge pull request #7914 from multiversx/cross-shard-gate
Cross shard gate
2 parents 543306c + 4fe087b commit cd57dd1

25 files changed

Lines changed: 890 additions & 32 deletions

common/common.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,3 +640,13 @@ func GetFeePayer(tx data.TransactionHandler) []byte {
640640

641641
return tx.GetSndAddr()
642642
}
643+
644+
// IsContendedHeader returns true if rounds were skipped between the parent and the header, so a
645+
// competing proof could exist at the header's nonce in a skipped round
646+
func IsContendedHeader(header data.HeaderHandler, parentHeader data.HeaderHandler) bool {
647+
if check.IfNil(header) || check.IfNil(parentHeader) {
648+
return false
649+
}
650+
651+
return header.GetRound() > parentHeader.GetRound()+1
652+
}

common/common_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,3 +1372,19 @@ func TestPrepareUnexecutableTxHashesKey(t *testing.T) {
13721372
hash := []byte("hash")
13731373
require.Equal(t, []byte("unexecutablehash"), common.PrepareUnexecutableTxHashesKey(hash))
13741374
}
1375+
1376+
func TestIsContendedHeader(t *testing.T) {
1377+
t.Parallel()
1378+
1379+
parent := &testscommon.HeaderHandlerStub{RoundField: 1}
1380+
1381+
require.False(t, common.IsContendedHeader(nil, parent))
1382+
require.False(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 5}, nil))
1383+
1384+
// consecutive round: no skipped round to hide a competitor in
1385+
require.False(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 2}, parent))
1386+
1387+
// at least one skipped round: a competing proof could exist there
1388+
require.True(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 3}, parent))
1389+
require.True(t, common.IsContendedHeader(&testscommon.HeaderHandlerStub{RoundField: 10}, parent))
1390+
}

factory/mock/blockTrackerStub.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type BlockTrackerStub struct {
4141
ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool
4242
ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64)
4343
IsHeaderQuarantinedCalled func(hash []byte) bool
44+
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
4445
}
4546

4647
// AddTrackedHeader -
@@ -327,3 +328,11 @@ func (bts *BlockTrackerStub) Close() error {
327328
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
328329
return bts == nil
329330
}
331+
332+
// IsSettledCrossHeader -
333+
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
334+
if bts.IsSettledCrossHeaderCalled != nil {
335+
return bts.IsSettledCrossHeaderCalled(header, headerHash)
336+
}
337+
return false
338+
}

integrationTests/mock/blockTrackerStub.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type BlockTrackerStub struct {
4242
ShouldAddHeaderCalled func(headerHandler data.HeaderHandler) bool
4343
ComputeOwnShardStuckCalled func(lastExecutionResultsInfo data.BaseExecutionResultHandler, currentNonce uint64)
4444
IsHeaderQuarantinedCalled func(hash []byte) bool
45+
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
4546
}
4647

4748
// AddTrackedHeader -
@@ -327,3 +328,11 @@ func (bts *BlockTrackerStub) Close() error {
327328
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
328329
return bts == nil
329330
}
331+
332+
// IsSettledCrossHeader -
333+
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
334+
if bts.IsSettledCrossHeaderCalled != nil {
335+
return bts.IsSettledCrossHeaderCalled(header, headerHash)
336+
}
337+
return false
338+
}

node/mock/blockTrackerStub.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type BlockTrackerStub struct {
4141
CheckProofAgainstFinalCalled func(proof data.HeaderProofHandler) error
4242
CheckProofAgainstRoundHandlerCalled func(proof data.HeaderProofHandler) error
4343
IsHeaderQuarantinedCalled func(hash []byte) bool
44+
IsSettledCrossHeaderCalled func(header data.HeaderHandler, headerHash []byte) bool
4445
}
4546

4647
// CheckProofAgainstFinal -
@@ -325,3 +326,11 @@ func (bts *BlockTrackerStub) Close() error {
325326
func (bts *BlockTrackerStub) IsInterfaceNil() bool {
326327
return bts == nil
327328
}
329+
330+
// IsSettledCrossHeader -
331+
func (bts *BlockTrackerStub) IsSettledCrossHeader(header data.HeaderHandler, headerHash []byte) bool {
332+
if bts.IsSettledCrossHeaderCalled != nil {
333+
return bts.IsSettledCrossHeaderCalled(header, headerHash)
334+
}
335+
return false
336+
}

process/block/baseProcess.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4382,3 +4382,38 @@ func (bp *baseProcessor) pruneTrieForHeadersUnprotected(
43824382

43834383
return nil
43844384
}
4385+
4386+
// isContendedUnsettledCrossHeader applies the R-CROSS referencing gate: a cross-shard header that
4387+
// skipped a round after its parent is not includable until a proofed child settles it
4388+
func (bp *baseProcessor) isContendedUnsettledCrossHeader(header data.HeaderHandler, parentHeader data.HeaderHandler, headerHash []byte) bool {
4389+
if !bp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) {
4390+
return false
4391+
}
4392+
if !common.IsContendedHeader(header, parentHeader) {
4393+
return false
4394+
}
4395+
4396+
return !bp.blockTracker.IsSettledCrossHeader(header, headerHash)
4397+
}
4398+
4399+
// checkNotContendedUnsettled errors when a referenced cross-shard header is contended and not yet
4400+
// settled; the header hash is computed only on the contended path
4401+
func (bp *baseProcessor) checkNotContendedUnsettled(header data.HeaderHandler, parentHeader data.HeaderHandler) error {
4402+
if !bp.enableEpochsHandler.IsFlagEnabled(common.SupernovaFlag) {
4403+
return nil
4404+
}
4405+
if !common.IsContendedHeader(header, parentHeader) {
4406+
return nil
4407+
}
4408+
4409+
headerHash, err := bp.getHeaderHash(header)
4410+
if err != nil {
4411+
return err
4412+
}
4413+
4414+
if !bp.blockTracker.IsSettledCrossHeader(header, headerHash) {
4415+
return fmt.Errorf("%w with hash %x", errIncludedContendedUnsettledHeader, headerHash)
4416+
}
4417+
4418+
return nil
4419+
}

process/block/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ var errNilPreviousHeader = errors.New("nil previous header")
1111
var errInvalidMiniBlocks = errors.New("invalid mini blocks")
1212

1313
var errIncludedQuarantinedHeader = errors.New("included quarantined header")
14+
15+
var errIncludedContendedUnsettledHeader = errors.New("included contended header not yet settled")

process/block/metablock.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,11 @@ func (mp *metaProcessor) checkShardHeadersValidity(metaHdr data.MetaHeaderHandle
22262226
if err != nil {
22272227
return nil, fmt.Errorf("%w : checkShardHeadersValidity -> isHdrConstructionValid", err)
22282228
}
2229+
2230+
err = mp.checkNotContendedUnsettled(shardHdr, lastCrossNotarizedHeader[shardID])
2231+
if err != nil {
2232+
return nil, fmt.Errorf("%w : checkShardHeadersValidity", err)
2233+
}
22292234
}
22302235

22312236
lastCrossNotarizedHeader[shardID] = shardHdr

process/block/metablockProposal.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,10 @@ func (mp *metaProcessor) checkHeadersSequenceCorrectness(hdrsForShard []ShardHea
12091209
return fmt.Errorf("%w with hash %x", errIncludedQuarantinedHeader, shardHdrInfo.Hash)
12101210
}
12111211

1212+
if mp.isContendedUnsettledCrossHeader(shardHdrInfo.Header, lastNotarizedHeaderInfoForShard.Header, shardHdrInfo.Hash) {
1213+
return fmt.Errorf("%w with hash %x", errIncludedContendedUnsettledHeader, shardHdrInfo.Hash)
1214+
}
1215+
12121216
err = mp.headerValidator.IsHeaderConstructionValid(shardHdrInfo.Header, lastNotarizedHeaderInfoForShard.Header)
12131217
if err != nil {
12141218
return err

process/block/metablockProposal_test.go

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/multiversx/mx-chain-go/process/mock"
3232
"github.com/multiversx/mx-chain-go/testscommon"
3333
dataRetrieverMock "github.com/multiversx/mx-chain-go/testscommon/dataRetriever"
34+
"github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock"
3435
"github.com/multiversx/mx-chain-go/testscommon/mbSelection"
3536
"github.com/multiversx/mx-chain-go/testscommon/pool"
3637
"github.com/multiversx/mx-chain-go/testscommon/processMocks"
@@ -1788,7 +1789,8 @@ func Test_checkShardHeadersValidityAndFinalityProposal(t *testing.T) {
17881789
dataPoolMock.SetHeadersPool(headersPoolMock)
17891790

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

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

19521955
mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
1953-
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
1956+
"shardCoordinator": mock.NewOneShardCoordinatorMock(),
1957+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
19541958
"blockTracker": &mock.BlockTrackerMock{
19551959
GetLastCrossNotarizedHeaderCalled: func(_ uint32) (data.HeaderHandler, []byte, error) {
19561960
return &testscommon.HeaderHandlerStub{}, nil, nil
@@ -3583,7 +3587,8 @@ func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) {
35833587
return expectedErr
35843588
},
35853589
},
3586-
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
3590+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
3591+
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
35873592
})
35883593
require.Nil(t, err)
35893594

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

3630+
t.Run("should return error for contended unsettled header", func(t *testing.T) {
3631+
t.Parallel()
3632+
3633+
contendedHash := []byte("contended hash")
3634+
mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
3635+
"headerValidator": &processMocks.HeaderValidatorMock{
3636+
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
3637+
return nil
3638+
},
3639+
},
3640+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
3641+
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
3642+
return flag == common.SupernovaFlag
3643+
},
3644+
},
3645+
"blockTracker": &integrationTestsMock.BlockTrackerStub{
3646+
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
3647+
return false
3648+
},
3649+
},
3650+
})
3651+
require.Nil(t, err)
3652+
3653+
err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
3654+
{
3655+
// rounds 2-4 skipped after the last notarized shard header
3656+
Header: &block.Header{Nonce: 2, Round: 5},
3657+
Hash: contendedHash,
3658+
},
3659+
}, blproc.ShardHeaderInfo{
3660+
Header: &block.Header{Nonce: 1, Round: 1},
3661+
})
3662+
require.ErrorContains(t, err, "included contended header not yet settled")
3663+
})
3664+
3665+
t.Run("should work for contended settled header", func(t *testing.T) {
3666+
t.Parallel()
3667+
3668+
mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
3669+
"headerValidator": &processMocks.HeaderValidatorMock{
3670+
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
3671+
return nil
3672+
},
3673+
},
3674+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
3675+
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
3676+
return flag == common.SupernovaFlag
3677+
},
3678+
},
3679+
"blockTracker": &integrationTestsMock.BlockTrackerStub{
3680+
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
3681+
return true
3682+
},
3683+
},
3684+
})
3685+
require.Nil(t, err)
3686+
3687+
err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
3688+
{
3689+
Header: &block.Header{Nonce: 2, Round: 5},
3690+
Hash: []byte("contended settled hash"),
3691+
},
3692+
}, blproc.ShardHeaderInfo{
3693+
Header: &block.Header{Nonce: 1, Round: 1},
3694+
})
3695+
require.Nil(t, err)
3696+
})
3697+
3698+
t.Run("should work for non-contended header without settlement lookup", func(t *testing.T) {
3699+
t.Parallel()
3700+
3701+
mp, err := blproc.ConstructPartialMetaBlockProcessorForTest(map[string]interface{}{
3702+
"headerValidator": &processMocks.HeaderValidatorMock{
3703+
IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error {
3704+
return nil
3705+
},
3706+
},
3707+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{
3708+
IsFlagEnabledCalled: func(flag core.EnableEpochFlag) bool {
3709+
return flag == common.SupernovaFlag
3710+
},
3711+
},
3712+
"blockTracker": &integrationTestsMock.BlockTrackerStub{
3713+
IsSettledCrossHeaderCalled: func(header data.HeaderHandler, headerHash []byte) bool {
3714+
require.Fail(t, "settlement must not be checked on the clean path")
3715+
return false
3716+
},
3717+
},
3718+
})
3719+
require.Nil(t, err)
3720+
3721+
err = mp.CheckHeadersSequenceCorrectness([]blproc.ShardHeaderInfo{
3722+
{
3723+
Header: &block.Header{Nonce: 2, Round: 2},
3724+
Hash: []byte("clean hash"),
3725+
},
3726+
}, blproc.ShardHeaderInfo{
3727+
Header: &block.Header{Nonce: 1, Round: 1},
3728+
})
3729+
require.Nil(t, err)
3730+
})
3731+
36253732
t.Run("should work", func(t *testing.T) {
36263733
t.Parallel()
36273734

@@ -3636,7 +3743,8 @@ func TestMetaProcessor_checkHeadersSequenceCorrectness(t *testing.T) {
36363743
return nil
36373744
},
36383745
},
3639-
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
3746+
"enableEpochsHandler": &enableEpochsHandlerMock.EnableEpochsHandlerStub{},
3747+
"blockTracker": &integrationTestsMock.BlockTrackerStub{},
36403748
})
36413749
require.Nil(t, err)
36423750

0 commit comments

Comments
 (0)