From a5c10eec2f7852cfc5146e96c4e663ce08fbb821 Mon Sep 17 00:00:00 2001 From: BeniaminDrasovean Date: Mon, 8 Jun 2026 15:39:05 +0300 Subject: [PATCH 1/4] disable outgoing txs on processing during supernova transition --- process/coordinator/process.go | 15 +++++++++++++++ process/errors.go | 3 +++ 2 files changed, 18 insertions(+) diff --git a/process/coordinator/process.go b/process/coordinator/process.go index 9d387d8f1d..424f263820 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) && + len(miniBlocksFromMe) > 0 { + return process.ErrOutgoingTxsDisabled + } startTime = time.Now() err = tc.processMiniBlocksFromMe(header, &block.Body{MiniBlocks: miniBlocksFromMe}, haveTime) elapsedTime = time.Since(startTime) @@ -381,6 +386,16 @@ 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 +} + // GetCreatedMiniBlocksFromMe returns the created mini blocks from me func (tc *transactionCoordinator) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { miniBlocks := make(block.MiniBlockSlice, 0) diff --git a/process/errors.go b/process/errors.go index 914ee1f927..6437b21b59 100644 --- a/process/errors.go +++ b/process/errors.go @@ -1508,3 +1508,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") From 932ce8cd4b420d80c3063ecc935738deccf2e3ba Mon Sep 17 00:00:00 2001 From: BeniaminDrasovean Date: Mon, 8 Jun 2026 15:50:05 +0300 Subject: [PATCH 2/4] check for forbidden outgoing miniblocks --- process/coordinator/process.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/process/coordinator/process.go b/process/coordinator/process.go index 424f263820..ac61a89a31 100644 --- a/process/coordinator/process.go +++ b/process/coordinator/process.go @@ -370,7 +370,7 @@ func (tc *transactionCoordinator) ProcessBlockTransaction( miniBlocksFromMe := body.MiniBlocks[mbIndex:] if !header.IsHeaderV3() && shouldDisableOutgoingTxs(tc.enableEpochsHandler, tc.enableRoundsHandler, header) && - len(miniBlocksFromMe) > 0 { + hasForbiddenOutgoingTxMiniBlocks(tc.shardCoordinator.SelfId(), miniBlocksFromMe) { return process.ErrOutgoingTxsDisabled } startTime = time.Now() @@ -396,6 +396,18 @@ func shouldDisableOutgoingTxs( 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 { + return true + } + } + return false +} + // GetCreatedMiniBlocksFromMe returns the created mini blocks from me func (tc *transactionCoordinator) GetCreatedMiniBlocksFromMe() block.MiniBlockSlice { miniBlocks := make(block.MiniBlockSlice, 0) From 49d453330e0b3275ee1de7fd29e8d328f60d3b2b Mon Sep 17 00:00:00 2001 From: BeniaminDrasovean Date: Mon, 8 Jun 2026 16:08:43 +0300 Subject: [PATCH 3/4] add unit tests --- process/coordinator/process_test.go | 110 ++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index 20e7e75b07..92211ff2c5 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -2048,6 +2048,116 @@ 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 + } + + selfShardID := tc.shardCoordinator.SelfId() + receiverShardID := (selfShardID + 1) % tc.shardCoordinator.NumberOfShards() + miniBlock := &block.MiniBlock{ + SenderShardID: selfShardID, + ReceiverShardID: receiverShardID, + Type: block.TxBlock, + 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}, + }, + } + + err = tc.ProcessBlockTransaction(header, body, haveTime) + require.ErrorIs(t, err, process.ErrOutgoingTxsDisabled) +} + +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() From 9861f4e2f8ae9672c76e9d55b73045852bb7a7c8 Mon Sep 17 00:00:00 2001 From: BeniaminDrasovean Date: Tue, 16 Jun 2026 18:12:59 +0300 Subject: [PATCH 4/4] fixes after review --- process/coordinator/process.go | 2 +- process/coordinator/process_test.go | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/process/coordinator/process.go b/process/coordinator/process.go index 6a40c28f16..195e9109c8 100644 --- a/process/coordinator/process.go +++ b/process/coordinator/process.go @@ -401,7 +401,7 @@ func hasForbiddenOutgoingTxMiniBlocks(selfShardID uint32, miniBlocks block.MiniB if mb.SenderShardID != selfShardID { continue } - if mb.Type == block.TxBlock { + if mb.Type == block.TxBlock || mb.Type == block.InvalidBlock { return true } } diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index b6fbe04a69..b467c81007 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -2070,12 +2070,21 @@ func TestTransactionCoordinator_ProcessBlockTransactionRejectsForbiddenOutgoingT return time.Second } - selfShardID := tc.shardCoordinator.SelfId() - receiverShardID := (selfShardID + 1) % tc.shardCoordinator.NumberOfShards() + 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: selfShardID, + SenderShardID: tc.shardCoordinator.SelfId(), ReceiverShardID: receiverShardID, - Type: block.TxBlock, + Type: blockType, TxHashes: [][]byte{txHash}, } miniBlockHash, err := core.CalculateHash(tc.marshalizer, tc.hasher, miniBlock) @@ -2090,8 +2099,7 @@ func TestTransactionCoordinator_ProcessBlockTransactionRejectsForbiddenOutgoingT }, } - err = tc.ProcessBlockTransaction(header, body, haveTime) - require.ErrorIs(t, err, process.ErrOutgoingTxsDisabled) + return header, body } func TestTransactionCoordinator_ProcessBlockTransactionAllowsPostProcessMiniBlocksDuringSupernovaTransition(t *testing.T) {