diff --git a/process/coordinator/process.go b/process/coordinator/process.go index 728084ccc1..195e9109c8 100644 --- a/process/coordinator/process.go +++ b/process/coordinator/process.go @@ -368,6 +368,11 @@ func (tc *transactionCoordinator) ProcessBlockTransaction( } miniBlocksFromMe := body.MiniBlocks[mbIndex:] + if !header.IsHeaderV3() && + shouldDisableOutgoingTxs(tc.enableEpochsHandler, tc.enableRoundsHandler, header) && + hasForbiddenOutgoingTxMiniBlocks(tc.shardCoordinator.SelfId(), miniBlocksFromMe) { + return process.ErrOutgoingTxsDisabled + } startTime = time.Now() err = tc.processMiniBlocksFromMe(header, &block.Body{MiniBlocks: miniBlocksFromMe}, haveTime) elapsedTime = time.Since(startTime) @@ -381,6 +386,28 @@ func (tc *transactionCoordinator) ProcessBlockTransaction( return nil } +func shouldDisableOutgoingTxs( + enableEpochsHandler common.EnableEpochsHandler, + enableRoundsHandler common.EnableRoundsHandler, + header data.HeaderHandler, +) bool { + isSupernovaEnabled := enableEpochsHandler.IsFlagEnabledInEpoch(common.SupernovaFlag, header.GetEpoch()) + supernovaRoundEnabled := enableRoundsHandler.IsFlagEnabledInRound(common.SupernovaRoundFlag, header.GetRound()) + return isSupernovaEnabled && !supernovaRoundEnabled +} + +func hasForbiddenOutgoingTxMiniBlocks(selfShardID uint32, miniBlocks block.MiniBlockSlice) bool { + for _, mb := range miniBlocks { + if mb.SenderShardID != selfShardID { + continue + } + if mb.Type == block.TxBlock || mb.Type == block.InvalidBlock { + return true + } + } + return false +} + // GetCreatedMiniBlocksFromMe returns the created mini blocks from me func (tc *transactionCoordinator) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { miniBlocks := make(block.MiniBlockSlice, 0) diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index 54fdd33fa7..b467c81007 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -2048,6 +2048,124 @@ func TestTransactionCoordinator_ProcessBlockTransaction(t *testing.T) { assert.Equal(t, process.ErrMissingTransaction, err) } +func TestTransactionCoordinator_ProcessBlockTransactionRejectsForbiddenOutgoingTxMiniBlocksDuringSupernovaTransition(t *testing.T) { + t.Parallel() + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() + argsTransactionCoordinator.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == 7 + }, + } + argsTransactionCoordinator.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) + require.NoError(t, err) + + haveTime := func() time.Duration { + return time.Second + } + + receiverShardID := (tc.shardCoordinator.SelfId() + 1) % tc.shardCoordinator.NumberOfShards() + header, body := getBodyAndHeader(t, tc, block.TxBlock, receiverShardID) + err = tc.ProcessBlockTransaction(header, body, haveTime) + require.ErrorIs(t, err, process.ErrOutgoingTxsDisabled) + + header, body = getBodyAndHeader(t, tc, block.InvalidBlock, tc.shardCoordinator.SelfId()) + err = tc.ProcessBlockTransaction(header, body, haveTime) + require.ErrorIs(t, err, process.ErrOutgoingTxsDisabled) +} + +func getBodyAndHeader(t *testing.T, tc *transactionCoordinator, blockType block.Type, receiverShardID uint32) (data.HeaderHandler, *block.Body) { + miniBlock := &block.MiniBlock{ + SenderShardID: tc.shardCoordinator.SelfId(), + ReceiverShardID: receiverShardID, + Type: blockType, + TxHashes: [][]byte{txHash}, + } + miniBlockHash, err := core.CalculateHash(tc.marshalizer, tc.hasher, miniBlock) + require.NoError(t, err) + + body := &block.Body{MiniBlocks: []*block.MiniBlock{miniBlock}} + header := &block.Header{ + Epoch: 7, + Round: 41, + MiniBlockHeaders: []block.MiniBlockHeader{ + {Hash: miniBlockHash, TxCount: 1, ReceiverShardID: receiverShardID}, + }, + } + + return header, body +} + +func TestTransactionCoordinator_ProcessBlockTransactionAllowsPostProcessMiniBlocksDuringSupernovaTransition(t *testing.T) { + t.Parallel() + + processCalled := false + preProcessor := &preprocMocks.PreProcessorMock{ + ProcessBlockTransactionsCalled: func(header data.HeaderHandler, body *block.Body, haveTime func() bool) error { + processCalled = true + require.Len(t, body.MiniBlocks, 1) + require.Equal(t, block.SmartContractResultBlock, body.MiniBlocks[0].Type) + return nil + }, + } + + argsTransactionCoordinator := createMockTransactionCoordinatorArguments() + argsTransactionCoordinator.PreProcessors = &preprocMocks.PreProcessorContainerMock{ + KeysCalled: func() []block.Type { + return []block.Type{block.SmartContractResultBlock} + }, + GetCalled: func(key block.Type) (process.PreProcessor, error) { + require.Equal(t, block.SmartContractResultBlock, key) + return preProcessor, nil + }, + } + argsTransactionCoordinator.EnableEpochsHandler = &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.SupernovaFlag && epoch == 7 + }, + } + argsTransactionCoordinator.EnableRoundsHandler = &testscommon.EnableRoundsHandlerStub{ + IsFlagEnabledInRoundCalled: func(flag common.EnableRoundFlag, round uint64) bool { + return false + }, + } + + tc, err := NewTransactionCoordinator(argsTransactionCoordinator) + require.NoError(t, err) + + haveTime := func() time.Duration { + return time.Second + } + + selfShardID := tc.shardCoordinator.SelfId() + miniBlock := &block.MiniBlock{ + SenderShardID: selfShardID, + ReceiverShardID: selfShardID, + Type: block.SmartContractResultBlock, + } + miniBlockHash, err := core.CalculateHash(tc.marshalizer, tc.hasher, miniBlock) + require.NoError(t, err) + + body := &block.Body{MiniBlocks: []*block.MiniBlock{miniBlock}} + header := &block.Header{ + Epoch: 7, + Round: 41, + MiniBlockHeaders: []block.MiniBlockHeader{ + {Hash: miniBlockHash, TxCount: 0, ReceiverShardID: selfShardID}, + }, + } + + err = tc.ProcessBlockTransaction(header, body, haveTime) + require.NoError(t, err) + require.True(t, processCalled) +} + func TestTransactionCoordinator_ProcessBlockTransaction_DoubleTxs(t *testing.T) { t.Parallel() diff --git a/process/errors.go b/process/errors.go index 68b2860c9d..00fbfc3c48 100644 --- a/process/errors.go +++ b/process/errors.go @@ -1541,3 +1541,6 @@ var ErrInvalidShardInfo = errors.New("invalid shard info") // ErrNilClosingNodeStartedFlag signals that the closing node started flag is nil var ErrNilClosingNodeStartedFlag = errors.New("closing node started flag is nil") + +// ErrOutgoingTxsDisabled signals that the outgoing transactions are disabled +var ErrOutgoingTxsDisabled = errors.New("outgoing transactions are disabled")