From b7d8db4446037a143a12cbf85143929f72df4f2a Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 16 Oct 2025 21:17:09 +0300 Subject: [PATCH 01/16] MX- 17245 Added ConstructPartialShardBlockProcessorForTest --- process/block/export_test.go | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/process/block/export_test.go b/process/block/export_test.go index 883e9cb94e8..46f18e034dd 100644 --- a/process/block/export_test.go +++ b/process/block/export_test.go @@ -1,8 +1,11 @@ package block import ( + "fmt" + "reflect" "sync" "time" + "unsafe" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" @@ -804,3 +807,48 @@ func (sp *shardProcessor) GetCrossShardIncomingMiniBlocksFromBody(body *block.Bo func GetHaveTimeForProposal(startTime time.Time, maxDuration time.Duration) func() time.Duration { return getHaveTimeForProposal(startTime, maxDuration) } + +func ConstructPartialShardBlockProcessorForTest(subcomponents map[string]interface{}) (*shardProcessor, error) { + bp := &baseProcessor{} + sp := &shardProcessor{baseProcessor: bp} + + setField := func(target any, name string, component any) error { + rv := reflect.ValueOf(target).Elem() + field := rv.FieldByName(name) + if !field.IsValid() { + return fmt.Errorf("invalid field: %s", name) + } + if !field.CanSet() { + // bypass export check (ok in tests, same package) + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + + val := reflect.ValueOf(component) + switch { + case val.Type().AssignableTo(field.Type()): + field.Set(val) + case val.Type().ConvertibleTo(field.Type()): + field.Set(val.Convert(field.Type())) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().AssignableTo(field.Type().Elem()): + ptr := reflect.New(val.Type()) + ptr.Elem().Set(val) + field.Set(ptr) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().ConvertibleTo(field.Type().Elem()): + ptr := reflect.New(field.Type().Elem()) + ptr.Elem().Set(val.Convert(field.Type().Elem())) + field.Set(ptr) + default: + return fmt.Errorf("cannot set field %s (got %s, expected %s)", name, val.Type(), field.Type()) + } + return nil + } + + for name, component := range subcomponents { + if err := setField(sp, name, component); err != nil { + if err2 := setField(sp.baseProcessor, name, component); err2 != nil { + return nil, err + } + } + } + return sp, nil +} From 2e0d6f38e580007c930514454db0d941b98ea23e Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Fri, 17 Oct 2025 02:39:54 +0300 Subject: [PATCH 02/16] MX- 17245 Added test for collectExecutionResults --- process/block/shardblockProposal_test.go | 202 +++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 62061bc8064..dbded59c558 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -2,6 +2,8 @@ package block_test import ( "bytes" + "fmt" + "math/big" "sync" "testing" "time" @@ -11,6 +13,9 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/state" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" retriever "github.com/multiversx/mx-chain-go/dataRetriever" @@ -25,10 +30,13 @@ import ( "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/dataRetriever" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" testscommonExecutionTrack "github.com/multiversx/mx-chain-go/testscommon/executionTrack" + "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/mbSelection" "github.com/multiversx/mx-chain-go/testscommon/pool" "github.com/multiversx/mx-chain-go/testscommon/processMocks" + stateMock "github.com/multiversx/mx-chain-go/testscommon/state" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -2475,3 +2483,197 @@ func TestShardProcessor_OnProposedBlock(t *testing.T) { require.True(t, wasOnProposedBlockCalled) }) } + +func TestShardProcessor_ShouldEpoch(t *testing.T) { + t.Parallel() + + _ = logger.SetLogLevel("*.TRACE") + epochStartTrigger := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { return 5 }, + IsEpochStartCalled: func() bool { return false }, + } + fmt.Println(epochStartTrigger.MetaEpoch()) + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "metaBlockFinality": uint32(10), + "epochStartTrigger": epochStartTrigger, + }) + + require.Nil(t, err) + require.NotNil(t, sp) + result := sp.ShouldEpochStartInfoBeAvailable(&testscommon.HeaderHandlerStub{ + IsStartOfEpochBlockCalled: func() bool { return true }, + EpochField: 10, + }) + require.True(t, result) +} + +func TestShardProcessor_collectExecutionResults(t *testing.T) { + t.Parallel() + logErr := logger.SetLogLevel("*:TRACE") + require.Nil(t, logErr) + t.Run("with CreateReceiptsHash error should return error", func(t *testing.T) { + t.Parallel() + + txCoordinator := &testscommon.TransactionCoordinatorMock{ + CreateReceiptsHashCalled: func() ([]byte, error) { + return nil, expectedErr + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "txCoordinator": txCoordinator, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + }) + require.Nil(t, err) + + header := &block.HeaderV3{} + _, err = sp.CollectExecutionResults(make([]byte, 0), header, &block.Body{}) + require.Equal(t, expectedErr, err) + }) + + t.Run("with gas used exceeds gas provided should return error", func(t *testing.T) { + t.Parallel() + + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + gasProvider := subComponents["gasConsumedProvider"].(*testscommon.GasHandlerStub) + gasProvider.TotalGasProvidedCalled = func() uint64 { + return 10 // less than gas used in test below + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Equal(t, process.ErrGasUsedExceedsGasProvided, err) + }) + + t.Run("should work", func(t *testing.T) { + t.Parallel() + _ = logger.SetLogLevel("*.DEBUG") + + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + + headerHash := []byte("header hash to be tested") + result, err := sp.CollectExecutionResults(headerHash, header, body) + require.Nil(t, err) + require.NotNil(t, result) + assert.Equal(t, uint64(1350), result.GetGasUsed(), "gas used should be set correctly") + assert.Equal(t, uint32(10), result.GetHeaderEpoch(), "epoch should be 10 as per mock header") + assert.Equal(t, headerHash, result.GetHeaderHash(), "header hash should match input") + assert.Equal(t, uint64(155), result.GetHeaderNonce(), "nonce should be 155 as per mock header") + assert.Equal(t, uint64(2067), result.GetHeaderRound(), "round should be 2067 as per mock header") + assert.Equal(t, []byte("root hash to be tested"), result.GetRootHash(), "root hash should match mock") + }) +} + +func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{}, data.HeaderHandler, *block.Body) { + + txCoordinator := &testscommon.TransactionCoordinatorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("tx1"), + []byte("tx2"), + }, + Reserved: []byte("reserved field"), + }, + &block.MiniBlock{ + TxHashes: [][]byte{ + []byte("tx3"), + []byte("tx4"), + []byte("tx5"), + }, + Reserved: []byte("reserved field"), + }, + } + }, + } + + rootHash := []byte("root hash to be tested") + accounts := map[state.AccountsDbIdentifier]state.AccountsAdapter{ + 0: &stateMock.AccountsStub{ + RootHashCalled: func() ([]byte, error) { + return rootHash, nil + }, + }, + } + feeHandler := &mock.FeeAccumulatorStub{ + GetAccumulatedFeesCalled: func() *big.Int { + return big.NewInt(1000) + }, + GetDeveloperFeesCalled: func() *big.Int { + return big.NewInt(100) + }, + } + gasConsumedProvider := &testscommon.GasHandlerStub{ + TotalGasProvidedCalled: func() uint64 { + return 1500 + }, + TotalGasRefundedCalled: func() uint64 { + return 100 + }, + TotalGasPenalizedCalled: func() uint64 { + return 50 + }, + } + subComponents := map[string]interface{}{ + "txCoordinator": txCoordinator, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "feeHandler": feeHandler, + "gasConsumedProvider": gasConsumedProvider, + "marshalizer": &mock.MarshalizerMock{}, + "hasher": &hashingMocks.HasherMock{}, + "enableEpochsHandler": enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + "dataPool": initDataPool(), + "accountsDB": accounts, + } + + header := &block.HeaderV3{ + Epoch: 10, + Nonce: 155, + Round: 2067, + TxCount: 5, + } + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx1")}, + }, + { + SenderShardID: 1, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx2")}, + }, + { + SenderShardID: 0, + ReceiverShardID: 1, + TxHashes: [][]byte{[]byte("tx3")}, + }, + { + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx4")}, + }, + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx5")}, + }, + }, + } + + return subComponents, header, body +} From 8699e6541dee014d5c8c13f686343d1c4a50bd3d Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Fri, 17 Oct 2025 11:06:06 +0300 Subject: [PATCH 03/16] MX-17245 Added unit tests for marshaller errors --- process/block/shardblockProposal_test.go | 68 +++++++++++++++--------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index dbded59c558..57360ad89f5 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -2,7 +2,7 @@ package block_test import ( "bytes" - "fmt" + "errors" "math/big" "sync" "testing" @@ -2484,30 +2484,6 @@ func TestShardProcessor_OnProposedBlock(t *testing.T) { }) } -func TestShardProcessor_ShouldEpoch(t *testing.T) { - t.Parallel() - - _ = logger.SetLogLevel("*.TRACE") - epochStartTrigger := &mock.EpochStartTriggerStub{ - MetaEpochCalled: func() uint32 { return 5 }, - IsEpochStartCalled: func() bool { return false }, - } - fmt.Println(epochStartTrigger.MetaEpoch()) - - sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ - "metaBlockFinality": uint32(10), - "epochStartTrigger": epochStartTrigger, - }) - - require.Nil(t, err) - require.NotNil(t, sp) - result := sp.ShouldEpochStartInfoBeAvailable(&testscommon.HeaderHandlerStub{ - IsStartOfEpochBlockCalled: func() bool { return true }, - EpochField: 10, - }) - require.True(t, result) -} - func TestShardProcessor_collectExecutionResults(t *testing.T) { t.Parallel() logErr := logger.SetLogLevel("*:TRACE") @@ -2541,7 +2517,7 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() gasProvider := subComponents["gasConsumedProvider"].(*testscommon.GasHandlerStub) gasProvider.TotalGasProvidedCalled = func() uint64 { - return 10 // less than gas used in test below + return 10 // less than gas used in test } sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) require.Nil(t, err) @@ -2550,6 +2526,46 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { assert.Equal(t, process.ErrGasUsedExceedsGasProvided, err) }) + t.Run("with cacheExecutedMiniBlocks error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("MarshalizerMock generic error") + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + marshaller := subComponents["marshalizer"].(*mock.MarshalizerMock) + marshaller.Fail = true + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + + t.Run("with cacheIntermediateTxsForHeader error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("cacheIntermediateTxsForHeader error") + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + + marshallerMock := subComponents["marshalizer"].(*mock.MarshalizerMock) + marshaller := &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + if _, ok := obj.(map[block.Type]map[string]data.TransactionHandler); ok { + return nil, expectedErr + } else { + return marshallerMock.Marshal(obj) + } + }} + subComponents["marshalizer"] = marshaller + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + t.Run("should work", func(t *testing.T) { t.Parallel() _ = logger.SetLogLevel("*.DEBUG") From 963ed5ad36464342c1a5243c8fe32bc25b4116cd Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Mon, 20 Oct 2025 03:39:50 +0300 Subject: [PATCH 04/16] MX-17245 Added different miniblocks types --- process/block/shardblockProposal_test.go | 177 ++++++++++++++++++++--- 1 file changed, 158 insertions(+), 19 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 57360ad89f5..6b25be4813d 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -3,6 +3,7 @@ package block_test import ( "bytes" "errors" + "fmt" "math/big" "sync" "testing" @@ -2577,6 +2578,7 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { headerHash := []byte("header hash to be tested") result, err := sp.CollectExecutionResults(headerHash, header, body) + require.Nil(t, err) require.NotNil(t, result) assert.Equal(t, uint64(1350), result.GetGasUsed(), "gas used should be set correctly") @@ -2585,6 +2587,65 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { assert.Equal(t, uint64(155), result.GetHeaderNonce(), "nonce should be 155 as per mock header") assert.Equal(t, uint64(2067), result.GetHeaderRound(), "round should be 2067 as per mock header") assert.Equal(t, []byte("root hash to be tested"), result.GetRootHash(), "root hash should match mock") + + realResult := result.(*block.ExecutionResult) + realResult.GetMiniBlockHeaders() + actual_miniblocks := map[block.Type]int{} + expected_miniblocks := map[block.Type]int{ + block.TxBlock: 4, + block.SmartContractResultBlock: 2, + block.ReceiptBlock: 1, + block.RewardsBlock: 1, + block.InvalidBlock: 1, + block.PeerBlock: 1, + } + for i, mbResult := range realResult.GetMiniBlockHeaders() { + switch mbResult.GetType() { + case block.TxBlock: + actual_miniblocks[block.TxBlock]++ + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "same-shard tx miniblock should have 2 txs as per mock") + } else if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "cross-shard tx miniblock should have 2 txs as per mock") + } else if mbResult.GetSenderShardID() == uint32(1) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") + } else if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") + } else { + require.Fail(t, "unexpected tx miniblock shard IDs") + } + case block.SmartContractResultBlock: + actual_miniblocks[block.SmartContractResultBlock]++ + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "outgoing SCR miniblock should have 1 tx as per mock") + } else if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + assert.Equal(t, uint32(2), mbResult.GetTxCount(), "incoming SCR miniblock should have 2 txs as per mock") + } else { + require.Fail(t, "unexpected SCR miniblock shard IDs") + } + case block.ReceiptBlock: + actual_miniblocks[block.ReceiptBlock]++ + assert.NotEqual(t, uint32(0), mbResult.GetSenderShardID(), "receipt miniblock should not be self-shard") + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "receipt miniblock should have 1 tx as per mock") + case block.RewardsBlock: + actual_miniblocks[block.RewardsBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "rewards miniblock should have 1 tx as per mock") + case block.InvalidBlock: + actual_miniblocks[block.InvalidBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "invalid miniblock should have 1 tx as per mock") + case block.PeerBlock: + actual_miniblocks[block.PeerBlock]++ + assert.Equal(t, uint32(1), mbResult.GetTxCount(), "peer miniblock should have 1 tx as per mock") + default: + //require.Fail(t, "unexpected miniblock type") + fmt.Println("MiniBlockHeader Result ", i, ": type ", mbResult.GetType(), ", sender shard ", mbResult.GetSenderShardID(), ", receiver shard ", mbResult.GetReceiverShardID(), ", tx count ", mbResult.GetTxCount()) + } + + fmt.Println("MiniBlockHeader Result ", i, ": type ", mbResult.GetType(), ", sender shard ", mbResult.GetSenderShardID(), ", receiver shard ", mbResult.GetReceiverShardID(), ", tx count ", mbResult.GetTxCount()) + } + assert.Equal(t, expected_miniblocks, actual_miniblocks, "miniblock counts by type should match expected") + assert.Equal(t, big.NewInt(1000), realResult.GetAccumulatedFees(), "accumulated fees should match mock") + assert.Equal(t, big.NewInt(100), realResult.GetDeveloperFees(), "developer fees should match mock") }) } @@ -2593,23 +2654,77 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} txCoordinator := &testscommon.TransactionCoordinatorMock{ GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { return block.MiniBlockSlice{ - &block.MiniBlock{ + // ✅ same-shard regular transactions + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, TxHashes: [][]byte{ - []byte("tx1"), - []byte("tx2"), + []byte("tx_self_3"), + []byte("tx_self_4"), }, - Reserved: []byte("reserved field"), }, - &block.MiniBlock{ + // ✅ cross-shard outgoing transactions + { + SenderShardID: 0, + ReceiverShardID: 1, + Type: block.TxBlock, TxHashes: [][]byte{ - []byte("tx3"), - []byte("tx4"), - []byte("tx5"), + []byte("tx_cross_3"), + []byte("tx_cross_4"), + }, + }, + // ⚠️ invalid transactions + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.InvalidBlock, + TxHashes: [][]byte{ + []byte("tx_invalid_1"), }, - Reserved: []byte("reserved field"), }, } }, + + CreatePostProcessMiniBlocksCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + // self shard SCRs - should be sanitized out + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{ + []byte("scr_self_1"), + []byte("scr_self_2"), + }, + }, + // outgoing SCRs + { + SenderShardID: 0, + ReceiverShardID: 1, + Type: block.SmartContractResultBlock, + TxHashes: [][]byte{ + []byte("scr_out_1"), + }, + }, + // self shard receipts - should be sanitized out + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.ReceiptBlock, + TxHashes: [][]byte{ + []byte("rcpt_0"), + }, + }, + } + }, + + CreateReceiptsHashCalled: func() ([]byte, error) { + return []byte("mock_receipts_hash"), nil + }, + GetAllIntermediateTxsCalled: func() map[block.Type]map[string]data.TransactionHandler { + return map[block.Type]map[string]data.TransactionHandler{} + }, } rootHash := []byte("root hash to be tested") @@ -2663,30 +2778,54 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} } body := &block.Body{ MiniBlocks: []*block.MiniBlock{ + //Outgoing miniblock from shard { - SenderShardID: 0, - ReceiverShardID: 0, - TxHashes: [][]byte{[]byte("tx1")}, + SenderShardID: 0, + TxHashes: [][]byte{ + []byte("tx_self_1"), + []byte("tx_self_2"), + []byte("tx_cross_1"), + []byte("tx_cross_2"), + []byte("tx_invalid_1"), + []byte("tx_unexecutable_1"), + }, }, + // Incoming miniblocks to shard { SenderShardID: 1, ReceiverShardID: 0, - TxHashes: [][]byte{[]byte("tx2")}, + TxHashes: [][]byte{[]byte("tx_incoming_1")}, }, { - SenderShardID: 0, - ReceiverShardID: 1, - TxHashes: [][]byte{[]byte("tx3")}, + SenderShardID: 2, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("tx_incoming_2")}, }, + //Incoming SCR miniblock { SenderShardID: 2, ReceiverShardID: 0, - TxHashes: [][]byte{[]byte("tx4")}, + TxHashes: [][]byte{[]byte("tx_scr_in_1"), []byte("tx_scr_in_2")}, + Type: block.SmartContractResultBlock, }, + // Other types of miniblocks { - SenderShardID: 0, + Type: block.RewardsBlock, + SenderShardID: core.MetachainShardId, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("reward1")}, + }, + { + Type: block.PeerBlock, + SenderShardID: core.MetachainShardId, + ReceiverShardID: 0, + TxHashes: [][]byte{[]byte("peer1")}, + }, + { + Type: block.ReceiptBlock, + SenderShardID: 2, ReceiverShardID: 0, - TxHashes: [][]byte{[]byte("tx5")}, + TxHashes: [][]byte{[]byte("rcpt_1")}, }, }, } From d129ab7f7a3b4a05d8001dbace8e2a8bdf66dab3 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Mon, 20 Oct 2025 19:30:22 +0300 Subject: [PATCH 05/16] MX-17245 Added unit tests for txCoordinator processProposal --- process/coordinator/processProposal.go | 10 +- process/coordinator/processProposal_test.go | 354 +++++++++++++++++++- process/coordinator/process_test.go | 2 +- 3 files changed, 359 insertions(+), 7 deletions(-) diff --git a/process/coordinator/processProposal.go b/process/coordinator/processProposal.go index 60f0e6640ba..cd6c65ce0e3 100644 --- a/process/coordinator/processProposal.go +++ b/process/coordinator/processProposal.go @@ -134,8 +134,8 @@ func (tc *transactionCoordinator) CreateMbsCrossShardDstMe( // if not all mini blocks were included, remove them from the miniBlocksAndHashes slice // but add them into pendingMiniBlocksAndHashes - if lastMBIndex != len(mbsSlice) { - pendingMiniBlocksAndHashes = miniBlocksAndHashes[lastMBIndex:] + if lastMBIndex+1 < len(mbsSlice) { + pendingMiniBlocksAndHashes = miniBlocksAndHashes[lastMBIndex+1:] miniBlocksAndHashes = miniBlocksAndHashes[:lastMBIndex+1] } @@ -174,17 +174,17 @@ func (tc *transactionCoordinator) SelectOutgoingTransactions() (selectedTxHashes } func (tc *transactionCoordinator) verifyCreatedMiniBlocksSanity(body *block.Body) error { - intraShardMbs := make([]*block.MiniBlock, 0) + miniblocksFromSelf := make([]*block.MiniBlock, 0) for _, mb := range body.MiniBlocks { if mb.SenderShardID == tc.shardCoordinator.SelfId() { - intraShardMbs = append(intraShardMbs, mb) + miniblocksFromSelf = append(miniblocksFromSelf, mb) } } collectedMbs := tc.GetCreatedMiniBlocksFromMe() unExecutableTransactions := tc.getUnExecutableTransactions() - allTxsInBody, err := collectTransactionsFromMiniBlocks(intraShardMbs) + allTxsInBody, err := collectTransactionsFromMiniBlocks(miniblocksFromSelf) if err != nil { return fmt.Errorf("%w: for body miniBlocks", err) } diff --git a/process/coordinator/processProposal_test.go b/process/coordinator/processProposal_test.go index a25f1f80d53..1b84362e272 100644 --- a/process/coordinator/processProposal_test.go +++ b/process/coordinator/processProposal_test.go @@ -205,6 +205,142 @@ func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing(t * require.False(t, executionPreprocessorCalled) } +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing_WithGasComputationError(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock mini block pool to return the test mini block + tc.miniBlockPool = &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true + } + return nil, false + }, + } + + // Mock the block data requester to return the mini block info + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos[:1] // Only first mini block + }, + } + + // Mock preprocessor to simulate no missing transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + proposalPreprocessorCalled = true + require.Equal(t, td.mb1Info.Miniblock, miniBlock) + return nil, 0 // No missing transactions + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + executionPreprocessorCalled = true + return nil, 0 + }, + } + + tc.gasComputation = &testscommon.GasComputationMock{ + CheckIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 0, errors.New("gas computation error") + }, + } + + miniBlocks, _, numTxs, allAdded, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Error(t, err) + require.Contains(t, err.Error(), "gas computation error") + require.Nil(t, miniBlocks) + require.Equal(t, uint32(0), numTxs) + require.False(t, allAdded) // No mini blocks were processed + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + +func TestTransactionCoordinator_CreateMbsCrossShardDstMe_MiniBlockProcessing_WithPendingMiniBlocks(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + td := createHeaderWithMiniBlocksAndTransactions() + + // Mock mini block pool to return the test mini block + tc.miniBlockPool = &cache.CacherStub{ + PeekCalled: func(key []byte) (value interface{}, ok bool) { + if reflect.DeepEqual(key, td.mb1Info.Hash) { + return td.mb1Info.Miniblock, true + } + return td.mb2Info.Miniblock, true + }, + } + + // Mock the block data requester to return the mini block info + tc.blockDataRequesterProposal = &preprocMocks.BlockDataRequesterStub{ + GetFinalCrossMiniBlockInfoAndRequestMissingCalled: func(header data.HeaderHandler) []*data.MiniBlockInfo { + return td.miniBlockInfos + }, + } + + // Mock preprocessor to simulate no missing transactions + proposalPreprocessorCalled := false + tc.preProcProposal.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + proposalPreprocessorCalled = true + return nil, 0 // No missing transactions + }, + } + + // Mock execution preprocessor to ensure it's not called + executionPreprocessorCalled := false + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetTransactionsAndRequestMissingForMiniBlockCalled: func(miniBlock *block.MiniBlock) ([]data.TransactionHandler, int) { + executionPreprocessorCalled = true + return nil, 0 + }, + } + + tc.gasComputation = &testscommon.GasComputationMock{ + CheckIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 1, nil // last mb added index is 0, so only first mini block is added, num pendings miniblocks is 1, so the second is pending + }, + } + + miniBlocks, pendingMiniBlocks, numTxs, allAdded, err := tc.CreateMbsCrossShardDstMe(td.hdr, nil) + + require.Nil(t, err) + + require.Equal(t, 1, len(miniBlocks)) + require.Equal(t, td.mb1Info.Miniblock, miniBlocks[0].Miniblock) + require.Equal(t, td.mb1Info.Hash, miniBlocks[0].Hash) + + require.Equal(t, 1, len(pendingMiniBlocks)) + require.Equal(t, td.mb2Info.Miniblock, pendingMiniBlocks[0].Miniblock) + require.Equal(t, td.mb2Info.Hash, pendingMiniBlocks[0].Hash) + + require.Equal(t, uint32(3), numTxs) + require.False(t, allAdded) + + // Verify proposal preprocessor was used, not execution + require.True(t, proposalPreprocessorCalled) + require.False(t, executionPreprocessorCalled) +} + func TestTransactionCoordinator_CreateMbsCrossShardDstMe_SkipShardOnMissingMiniBlock(t *testing.T) { t.Parallel() @@ -489,6 +625,41 @@ func TestTransactionCoordinator_SelectOutgoingTransactions_HandlesNilPreprocesso require.Equal(t, 0, len(txHashes)) } +func TestTransactionCoordinator_SelectOutgoingTransactions_CheckOutgoingTransactionsError(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + txHashesType1 := [][]byte{[]byte("tx_hash_1"), []byte("tx_hash_2")} + txHashesType2 := [][]byte{[]byte("tx_hash_3"), []byte("tx_hash_4")} + + // add transactions to the transactions pool + cacheId := process.ShardCacherIdentifier(0, 0) + ph.Transactions().AddData(txHashesType1[0], &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 0}, 100, cacheId) + ph.Transactions().AddData(txHashesType1[1], &transaction.Transaction{SndAddr: []byte("sender1"), Nonce: 1}, 100, cacheId) + + // add transactions to the unsigned transactions pool + ph.UnsignedTransactions().AddData(txHashesType2[0], &transaction.Transaction{SndAddr: []byte("sender2"), Nonce: 0}, 100, cacheId) + ph.UnsignedTransactions().AddData(txHashesType2[1], &transaction.Transaction{SndAddr: []byte("sender3"), Nonce: 0}, 100, cacheId) + + // Add both block types to the keys + tc.preProcProposal.keysTxPreProcs = []block.Type{block.TxBlock, block.SmartContractResultBlock} + tc.gasComputation = &testscommon.GasComputationMock{ + CheckOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return nil, nil, errors.New("test error in CheckOutgoingTransactions") + }, + } + + require.NotPanics(t, func() { + selectedTxHashes, selectedPendingIncomingMiniBlocks := tc.SelectOutgoingTransactions() + // Function should continue and return empty slice despite error + require.Nil(t, selectedTxHashes) + require.Nil(t, selectedPendingIncomingMiniBlocks) + }) +} + func TestTransactionCoordinator_CreateMbsCrossShardDstMe_ProcessedMiniBlocksInfo(t *testing.T) { t.Parallel() @@ -599,7 +770,139 @@ func TestTransactionCoordinator_verifyCreatedMiniBlocksSanity(t *testing.T) { err = tc.verifyCreatedMiniBlocksSanity(body) require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) }) - t.Run("should work", func(t *testing.T) { + + t.Run("error adding un-executable transactions to the collected transactions - should error", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + string([]byte("tx_unexec_hash")): {}, + } + }, + // make the same preprocessor also report the created miniblocks from me + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_unexec_hash"), + }, + }, + } + }, + } + require.Nil(t, err) + require.NotNil(t, tc) + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_unexec_hash"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) + }) + + t.Run("transactions mismatch should error", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.Nil(t, err) + require.NotNil(t, tc) + + // preprocessor says we created tx 'a' + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{} // no duplicates this time + }, + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + &block.MiniBlock{ + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_a"), + }, + }, + } + }, + } + + // but the block body includes tx 'b' + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_b"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.True(t, errors.Is(err, process.ErrTransactionsMismatch)) + }) + + t.Run("verifyCreatedMiniBlocksSanity should error on collectTransactionsFromMiniBlocks", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.NoError(t, err) + require.NotNil(t, tc) + + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_dup"), + []byte("tx_dup"), // duplicate triggers collectTransactionsFromMiniBlocks error + }, + }, + } + }, + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return nil + }, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{ + { + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{ + []byte("tx_dup"), + }, + }, + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.Error(t, err) + require.Contains(t, err.Error(), "for created miniBlocks") + require.True(t, errors.Is(err, process.ErrDuplicatedTransaction)) + }) + + t.Run("should work empty body", func(t *testing.T) { t.Parallel() ph := dataRetrieverMock.NewPoolsHolderMock() @@ -615,6 +918,55 @@ func TestTransactionCoordinator_verifyCreatedMiniBlocksSanity(t *testing.T) { err = tc.verifyCreatedMiniBlocksSanity(body) require.NoError(t, err) }) + t.Run("should work with unexecutable transactions", func(t *testing.T) { + t.Parallel() + + ph := dataRetrieverMock.NewPoolsHolderMock() + tc, err := createMockTransactionCoordinatorForProposalTests(ph) + require.NoError(t, err) + require.NotNil(t, tc) + + // first mini-block tx3 is un-executable + mb1 := &block.MiniBlock{ + SenderShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2"), []byte("tx_unexec")}, + } + // second mini-block incomming - will be ignored for created txs verification + mb2 := &block.MiniBlock{ + SenderShardID: 1, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx4")}, + } + + body := &block.Body{ + MiniBlocks: []*block.MiniBlock{mb1, mb2}, + } + + // Preprocessor still returns all created mini-blocks + tc.preProcExecution.txPreProcessors[block.TxBlock] = &preprocMocks.PreProcessorMock{ + GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { + return block.MiniBlockSlice{ + { + SenderShardID: 0, + ReceiverShardID: 0, + Type: block.TxBlock, + TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")}, + }, + } + }, + GetUnExecutableTransactionsCalled: func() map[string]struct{} { + return map[string]struct{}{ + string([]byte("tx_unexec")): {}, + } + }, + } + + err = tc.verifyCreatedMiniBlocksSanity(body) + require.NoError(t, err) + }) + } func createPreProcessorContainerWithPoolsHolder(poolsHolder dataRetriever.PoolsHolder) process.PreProcessorsContainer { diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index da316d2b668..44c6e436439 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -269,7 +269,7 @@ func createMockTransactionCoordinatorArguments() ArgTransactionCoordinator { return txHashes, nil, nil }, CheckIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { - return len(miniBlocks), 0, nil + return len(miniBlocks) - 1, 0, nil }, }, } From b65a18397e4796d59601c5a1334351039ef96a04 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Tue, 21 Oct 2025 13:52:56 +0300 Subject: [PATCH 06/16] MX-17245 Added coverage for addExecutionResultsOnHeader --- process/block/export_test.go | 4 + process/block/shardblockProposal_test.go | 238 ++++++++++++++++++++++- 2 files changed, 233 insertions(+), 9 deletions(-) diff --git a/process/block/export_test.go b/process/block/export_test.go index 25c98acd53b..2fa431df6c3 100644 --- a/process/block/export_test.go +++ b/process/block/export_test.go @@ -799,6 +799,10 @@ func (sp *shardProcessor) CollectExecutionResults(headerHash []byte, header data return sp.collectExecutionResults(headerHash, header, body) } +func (sp *shardProcessor) AddExecutionResultsOnHeader(shardHeader data.HeaderHandler) error { + return sp.addExecutionResultsOnHeader(shardHeader) +} + // GetCrossShardIncomingMiniBlocksFromBody - func (sp *shardProcessor) GetCrossShardIncomingMiniBlocksFromBody(body *block.Body) []*block.MiniBlock { return sp.getCrossShardIncomingMiniBlocksFromBody(body) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 6b25be4813d..1d848b69f30 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -14,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/state" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" @@ -37,6 +38,7 @@ import ( "github.com/multiversx/mx-chain-go/testscommon/mbSelection" "github.com/multiversx/mx-chain-go/testscommon/pool" "github.com/multiversx/mx-chain-go/testscommon/processMocks" + "github.com/multiversx/mx-chain-go/testscommon/round" stateMock "github.com/multiversx/mx-chain-go/testscommon/state" statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" ) @@ -613,6 +615,197 @@ func TestShardProcessor_CreateBlockProposal(t *testing.T) { }) } +func Test_addExecutionResultsOnHeader(t *testing.T) { + t.Parallel() + t.Run("executionResultsTracker returns error should error", func(t *testing.T) { + t.Parallel() + expectedErr := errors.New("expected error") + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return nil, expectedErr + }, + }, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + t.Run("GetPrevBlockLastExecutionResult returns error should error", func(t *testing.T) { + t.Parallel() + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + }, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, process.ErrNilBlockChain, err) + }) + t.Run("CreateDataForInclusionEstimation returns error should error", func(t *testing.T) { + t.Parallel() + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + } + }, + }, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{}) + require.Error(t, err) + require.Equal(t, process.ErrNilLastExecutionResultHandler, err) + }) + + t.Run("CreateLastExecutionResultInfoFromExecutionResult returns error should error", func(t *testing.T) { + t.Parallel() + logger.SetLogLevel("*:DEBUG") + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + // return one meta execution result (so Decide can include it) + meta := &block.MetaExecutionResult{ + ExecutionResult: &block.BaseMetaExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{ + HeaderHash: []byte("hdr-hash"), + HeaderNonce: 1, + HeaderRound: 2, + RootHash: []byte("root"), + GasUsed: 100000, + }, + ValidatorStatsRootHash: []byte("vstats"), + AccumulatedFeesInEpoch: big.NewInt(123), + DevFeesInEpoch: big.NewInt(45), + }, + ReceiptsHash: []byte{}, + MiniBlockHeaders: nil, + ExecutedTxCount: 0, + } + return []data.BaseExecutionResultHandler{meta}, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + }, + "executionResultsInclusionEstimator": &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *estimator.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 1 + }, + }, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{Round: 3}) + require.Error(t, err) + require.Equal(t, process.ErrWrongTypeAssertion, err) + }) + t.Run("will work with valid data", func(t *testing.T) { + t.Parallel() + logger.SetLogLevel("*:DEBUG") + + baseExecutionResults := &block.BaseExecutionResult{ + HeaderHash: []byte("hash"), + HeaderNonce: 100, + HeaderRound: 1, + RootHash: []byte("rootHash"), + } + header := &block.HeaderV3{ + PrevHash: []byte("prev_hash"), + + LastExecutionResult: &block.ExecutionResultInfo{ + NotarizedInRound: 1, + ExecutionResult: baseExecutionResults, + }, + } + + genesisTimeStampMs := uint64(1000) + roundTime := uint64(100) + roundHandler := &round.RoundHandlerMock{ + GetTimeStampForRoundCalled: func(round uint64) uint64 { + return genesisTimeStampMs + round*roundTime + }, + } + defaultCfg := config.ExecutionResultInclusionEstimatorConfig{ + SafetyMargin: 110, + MaxResultsPerBlock: 10, + } + + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ + GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { + return []data.BaseExecutionResultHandler{ + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 2, GasUsed: 100_000_000}, + }, + &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 999_000_000}, + }, + }, nil + }, + }, + "blockChain": &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderHashCalled: func() []byte { + return []byte("hash") + }, + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return header + }, + }, + "executionResultsInclusionEstimator": estimator.NewExecutionResultInclusionEstimator(defaultCfg, roundHandler), + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + }) + err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{Round: 3}) + require.NoError(t, err) + }) +} + func TestShardProcessor_SelectIncomingMiniBlocks(t *testing.T) { t.Parallel() @@ -2487,8 +2680,7 @@ func TestShardProcessor_OnProposedBlock(t *testing.T) { func TestShardProcessor_collectExecutionResults(t *testing.T) { t.Parallel() - logErr := logger.SetLogLevel("*:TRACE") - require.Nil(t, logErr) + t.Run("with CreateReceiptsHash error should return error", func(t *testing.T) { t.Parallel() @@ -2533,6 +2725,36 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { expectedErr := errors.New("MarshalizerMock generic error") subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + expected_blocks := 10 + marshallerCalled := 0 + marshallerMock := subComponents["marshalizer"].(*mock.MarshalizerMock) + marshaller := &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + // Marshaller is first called for mini block headers, then for executed mini blocks + // We want to fail on the executed mini blocks marshalling + + if marshallerCalled == expected_blocks { + marshallerMock.Fail = true + } + marshallerCalled++ + return marshallerMock.Marshal(obj) + }, + } + subComponents["marshalizer"] = marshaller + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) + require.Nil(t, err) + headerHash := []byte("header hash to be tested") + _, err = sp.CollectExecutionResults(headerHash, header, body) + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + + t.Run("with createMiniBlockHeaderHandlers error should return error", func(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("MarshalizerMock generic error") + subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() + marshaller := subComponents["marshalizer"].(*mock.MarshalizerMock) marshaller.Fail = true sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subComponents) @@ -2569,7 +2791,6 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { t.Run("should work", func(t *testing.T) { t.Parallel() - _ = logger.SetLogLevel("*.DEBUG") subComponents, header, body := createSubComponentsForCollectExecutionResultsTest() @@ -2637,8 +2858,7 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { actual_miniblocks[block.PeerBlock]++ assert.Equal(t, uint32(1), mbResult.GetTxCount(), "peer miniblock should have 1 tx as per mock") default: - //require.Fail(t, "unexpected miniblock type") - fmt.Println("MiniBlockHeader Result ", i, ": type ", mbResult.GetType(), ", sender shard ", mbResult.GetSenderShardID(), ", receiver shard ", mbResult.GetReceiverShardID(), ", tx count ", mbResult.GetTxCount()) + require.Fail(t, "unexpected miniblock type") } fmt.Println("MiniBlockHeader Result ", i, ": type ", mbResult.GetType(), ", sender shard ", mbResult.GetSenderShardID(), ", receiver shard ", mbResult.GetReceiverShardID(), ", tx count ", mbResult.GetTxCount()) @@ -2654,7 +2874,7 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} txCoordinator := &testscommon.TransactionCoordinatorMock{ GetCreatedMiniBlocksFromMeCalled: func() block.MiniBlockSlice { return block.MiniBlockSlice{ - // ✅ same-shard regular transactions + // same-shard regular transactions { SenderShardID: 0, ReceiverShardID: 0, @@ -2664,7 +2884,7 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} []byte("tx_self_4"), }, }, - // ✅ cross-shard outgoing transactions + // cross-shard outgoing transactions { SenderShardID: 0, ReceiverShardID: 1, @@ -2674,7 +2894,7 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} []byte("tx_cross_4"), }, }, - // ⚠️ invalid transactions + // invalid transactions { SenderShardID: 0, ReceiverShardID: 0, @@ -2774,7 +2994,7 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} Epoch: 10, Nonce: 155, Round: 2067, - TxCount: 5, + TxCount: 13, } body := &block.Body{ MiniBlocks: []*block.MiniBlock{ From 0028d95a6db0aa7448c096d3a3f48fec1576057a Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Tue, 21 Oct 2025 14:05:22 +0300 Subject: [PATCH 07/16] MX-17245 Linter fix --- process/block/shardblockProposal_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 1d848b69f30..d80713a3a81 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -16,7 +16,7 @@ import ( "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/state" - logger "github.com/multiversx/mx-chain-logger-go" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -678,7 +678,6 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { t.Run("CreateLastExecutionResultInfoFromExecutionResult returns error should error", func(t *testing.T) { t.Parallel() - logger.SetLogLevel("*:DEBUG") baseExecutionResults := &block.BaseExecutionResult{ HeaderHash: []byte("hash"), @@ -744,7 +743,6 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { }) t.Run("will work with valid data", func(t *testing.T) { t.Parallel() - logger.SetLogLevel("*:DEBUG") baseExecutionResults := &block.BaseExecutionResult{ HeaderHash: []byte("hash"), From bd554a11f10c6c76eef15474e71fe6802dec4ec0 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Wed, 22 Oct 2025 23:37:31 +0300 Subject: [PATCH 08/16] MX-17245 Added coverage for checkEpochCorectnessCrossChain, VerifyProposal --- process/block/shardblockProposal_test.go | 165 +++++++++++++++++++++-- process/block/shardblock_test.go | 128 ++++++++++++++++++ 2 files changed, 280 insertions(+), 13 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index d80713a3a81..6b3ed484d1f 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -14,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/block" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-go/common" + "github.com/multiversx/mx-chain-go/common/graceperiod" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/state" @@ -746,7 +747,7 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { baseExecutionResults := &block.BaseExecutionResult{ HeaderHash: []byte("hash"), - HeaderNonce: 100, + HeaderNonce: 3, HeaderRound: 1, RootHash: []byte("rootHash"), } @@ -771,16 +772,18 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { MaxResultsPerBlock: 10, } + executionResult1 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderHash: []byte("hash1"), HeaderNonce: 1, HeaderRound: 2, GasUsed: 100_000_000}, + } + executionResult2 := &block.ExecutionResult{ + BaseExecutionResult: &block.BaseExecutionResult{HeaderHash: []byte("hash2"), HeaderNonce: 2, HeaderRound: 2, GasUsed: 999_000_000}, + } sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { return []data.BaseExecutionResultHandler{ - &block.ExecutionResult{ - BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 1, HeaderRound: 2, GasUsed: 100_000_000}, - }, - &block.ExecutionResult{ - BaseExecutionResult: &block.BaseExecutionResult{HeaderNonce: 2, HeaderRound: 2, GasUsed: 999_000_000}, - }, + executionResult1, + executionResult2, }, nil }, }, @@ -799,8 +802,18 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { }, }, }) - err := sp.AddExecutionResultsOnHeader(&block.HeaderV3{Round: 3}) + + proposalHeader := &block.HeaderV3{Round: 3} + err := sp.AddExecutionResultsOnHeader(proposalHeader) + + // expected only first pending execution result to be added require.NoError(t, err) + actualLastExecutionResult := proposalHeader.GetLastExecutionResultHandler().(*block.ExecutionResultInfo) + assert.NotNil(t, actualLastExecutionResult) + assert.Equal(t, actualLastExecutionResult.ExecutionResult, executionResult1.BaseExecutionResult) + assert.NotNil(t, proposalHeader.ExecutionResults) + assert.Len(t, proposalHeader.ExecutionResults, 1) + assert.Equal(t, proposalHeader.ExecutionResults[0], executionResult1) }) } @@ -1331,6 +1344,45 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { require.Equal(t, expectedError, err) }) + t.Run("checkEpochCorectnessCrossChain fails should error", func(t *testing.T) { + t.Parallel() + + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["epochChangeGracePeriodHandler"] = &gracePeriodErrStub{} + subcomponents["epochStartTrigger"] = &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return 10 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 15 + }, + } + genesisNonce := uint64(0) + subcomponents["genesisNonce"] = genesisNonce + subcomponents["forkDetector"] = &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error", err.Error()) + }) t.Run("should work", func(t *testing.T) { t.Parallel() @@ -2988,11 +3040,18 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} "accountsDB": accounts, } + header, body := createHeaderAndBodyForTestingProcessBlockProposal() + + return subComponents, header, body +} + +func createHeaderAndBodyForTestingProcessBlockProposal() (data.HeaderHandler, *block.Body) { header := &block.HeaderV3{ - Epoch: 10, - Nonce: 155, - Round: 2067, - TxCount: 13, + PrevHash: []byte("previousHash"), + Epoch: 10, + Nonce: 155, + Round: 2067, + TxCount: 13, } body := &block.Body{ MiniBlocks: []*block.MiniBlock{ @@ -3047,6 +3106,86 @@ func createSubComponentsForCollectExecutionResultsTest() (map[string]interface{} }, }, } + return header, body +} + +func createSubComponentsForVerifyProposalTest() map[string]interface{} { + poolMock := initDataPool() + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 0, + Round: 1, + ShardID: 0, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) + blkc.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + blkc.SetCurrentBlockHeaderHash([]byte("hash")) + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + return map[string]interface{}{ + "blockChain": blkc, + "dataPool": poolMock, + "shardCoordinator": &mock.ShardCoordinatorStub{ + SelfIdCalled: func() uint32 { + return 0 + }, + }, + "executionResultsVerifier": &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + }, + "executionResultsInclusionEstimator": &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *estimator.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + }, + "missingDataResolver": &processMocks.MissingDataResolverMock{ + WaitForMissingDataCalled: func(timeout time.Duration) error { + return nil + }, + }, + "epochStartTrigger": &mock.EpochStartTriggerStub{ + EpochStartRoundCalled: func() uint64 { + return 10 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + }, + "enableEpochsHandler": enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + "epochChangeGracePeriodHandler": gracePeriod, + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, []byte("h"), nil + }, + }, + "gasComputation": &testscommon.GasComputationMock{ + CheckIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return len(miniBlocks) - 1, 0, nil // no pending mini blocks left + }, + CheckOutgoingTransactionsCalled: func(txHashes [][]byte, transactions []data.TransactionHandler) ([][]byte, []data.MiniBlockHeaderHandler, error) { + return txHashes, []data.MiniBlockHeaderHandler{&block.MiniBlockHeader{}}, nil // one pending mini block added + }, + }, + "appStatusHandler": &statusHandlerMock.AppStatusHandlerStub{}, + "marshalizer": &mock.MarshalizerMock{}, + "roundHandler": &mock.RoundHandlerMock{}, + } - return subComponents, header, body } diff --git a/process/block/shardblock_test.go b/process/block/shardblock_test.go index d275fcc0bd6..64d346050a1 100644 --- a/process/block/shardblock_test.go +++ b/process/block/shardblock_test.go @@ -22,6 +22,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" + "github.com/multiversx/mx-chain-go/common/graceperiod" "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/pool" vmcommon "github.com/multiversx/mx-chain-vm-common-go" @@ -4753,6 +4754,133 @@ func TestShardProcessor_updateStateStorage(t *testing.T) { assert.True(t, cancelPruneWasCalled) } +type gracePeriodErrStub struct{} + +func (gracePeriodErrStub) GetGracePeriodForEpoch(_ uint32) (uint32, error) { + return 0, errors.New("epochChangeGracePeriodHandler forced error") +} + +func (gracePeriodErrStub) IsInterfaceNil() bool { return false } + +func TestShardProcessor_checkEpochCorrectnessCrossChain_gracePeriodError(t *testing.T) { + t.Parallel() + + genesisNonce := uint64(0) + gracePeriod := &gracePeriodErrStub{} + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 1, + Round: 10, + } + }, + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 0, + Round: 10, + } + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis-hash") + }, + } + + epochStartTrigger := &mock.EpochStartTriggerStub{ + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + EpochStartRoundCalled: func() uint64 { + return 1 + }, + MetaEpochCalled: func() uint32 { + return 2 + }, + EpochCalled: func() uint32 { + return 1 + }, + } + + forkDetector := &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "genesisNonce": genesisNonce, + "blockChain": blockchain, + "epochStartTrigger": epochStartTrigger, + "forkDetector": forkDetector, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectnessCrossChain() + assert.NotNil(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error", err.Error()) + +} + +func TestShardProcessor_checkEpochCorrectnessCrossChain_NoRevertWhenFinalizedReached(t *testing.T) { + t.Parallel() + + genesisNonce := uint64(0) + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + blockchain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 1, + Round: 10, + } + }, + GetGenesisHeaderCalled: func() data.HeaderHandler { + return &block.Header{ + Nonce: 0, + Epoch: 0, + Round: 10, + } + }, + GetGenesisHeaderHashCalled: func() []byte { + return []byte("genesis-hash") + }, + } + + epochStartTrigger := &mock.EpochStartTriggerStub{ + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + EpochStartRoundCalled: func() uint64 { + return 1 + }, + MetaEpochCalled: func() uint32 { + return 2 + }, + EpochCalled: func() uint32 { + return 1 + }, + } + + forkDetector := &mock.ForkDetectorMock{ + GetHighestFinalBlockNonceCalled: func() uint64 { + return genesisNonce + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "genesisNonce": genesisNonce, + "blockChain": blockchain, + "epochStartTrigger": epochStartTrigger, + "forkDetector": forkDetector, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectnessCrossChain() + assert.Nil(t, err) + +} + func TestShardProcessor_checkEpochCorrectnessCrossChainNilCurrentBlock(t *testing.T) { t.Parallel() From 0ba3e0e4af9b05526a2df21b8854c60783c0f1ed Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Wed, 22 Oct 2025 23:44:30 +0300 Subject: [PATCH 09/16] MX-17245 Fixes --- process/block/shardblockProposal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 6b3ed484d1f..2abe6b8b4de 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -3133,7 +3133,7 @@ func createSubComponentsForVerifyProposalTest() map[string]interface{} { } blkc, _ := blockchain.NewBlockChain(&statusHandlerMock.AppStatusHandlerStub{}) - blkc.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + _ = blkc.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) blkc.SetCurrentBlockHeaderHash([]byte("hash")) gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) From 2f3a5cf3bb1a13d69aa9e1896b1eb890f894f2f7 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 23 Oct 2025 01:41:32 +0300 Subject: [PATCH 10/16] MX-17245added coverage for checkEpochCorectness, VerifyBlockProposal --- process/block/shardblockProposal_test.go | 25 +++ process/block/shardblock_test.go | 219 ++++++++++++++++++++++- 2 files changed, 242 insertions(+), 2 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 2abe6b8b4de..0f51b3d146f 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -1383,6 +1383,31 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { require.Error(t, err) assert.Equal(t, "epochChangeGracePeriodHandler forced error", err.Error()) }) + t.Run("checkEpochCorectness fails should error", func(t *testing.T) { + t.Parallel() + + subcomponents := createSubComponentsForVerifyProposalTest() + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + Epoch: 5, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Contains(t, err.Error(), "epoch does not match") + }) t.Run("should work", func(t *testing.T) { t.Parallel() diff --git a/process/block/shardblock_test.go b/process/block/shardblock_test.go index 64d346050a1..2edc3599322 100644 --- a/process/block/shardblock_test.go +++ b/process/block/shardblock_test.go @@ -4822,7 +4822,7 @@ func TestShardProcessor_checkEpochCorrectnessCrossChain_gracePeriodError(t *test } -func TestShardProcessor_checkEpochCorrectnessCrossChain_NoRevertWhenFinalizedReached(t *testing.T) { +func TestShardProcessor_checkEpochCorrectnessCrossChain_FinalizedReached(t *testing.T) { t.Parallel() genesisNonce := uint64(0) @@ -4878,7 +4878,6 @@ func TestShardProcessor_checkEpochCorrectnessCrossChain_NoRevertWhenFinalizedRea err = sp.CheckEpochCorrectnessCrossChain() assert.Nil(t, err) - } func TestShardProcessor_checkEpochCorrectnessCrossChainNilCurrentBlock(t *testing.T) { @@ -5253,6 +5252,56 @@ func TestShardProcessor_RequestMetaHeadersIfNeededShouldAddHeaderIntoTrackerPool assert.Equal(t, expectedAddedNonces, addedNonces) } +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenHeaderEpochBehindCurrentHeader(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 1, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with older epoch 1 than blockchain epoch 3", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsStartOfEpochButShouldNotBe(t *testing.T) { + t.Parallel() + + // set EpochStartMetaHash non-nil to emulate IsStartOfEpochBlock == true + header := &block.Header{ + Epoch: 3, + EpochStartMetaHash: []byte("start"), + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with same epoch 3 as blockchain and it is of epoch start", err.Error()) +} + func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochMetaBlockWhenEpochDoesNotMatch(t *testing.T) { t.Parallel() @@ -5328,6 +5377,172 @@ func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochM assert.True(t, errors.Is(err, process.ErrEpochDoesNotMatch)) } +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsHeaderOfInvalidEpoch(t *testing.T) { + t.Parallel() + + // isHeaderOfInvalidEpoch := header.GetEpoch() > sp.epochStartTrigger.MetaEpoch() + header := &block.Header{ + Epoch: 3, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 2 + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with epoch too high 3 with trigger in epoch 2", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenEpochChangeGracePeriodHandlerErrors(t *testing.T) { + t.Parallel() + + header := &block.Header{ + Epoch: 3, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 3 + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": &gracePeriodErrStub{}, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epochChangeGracePeriodHandler forced error could not get grace period for epoch 3", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsOldEpochAndShouldBeNew(t *testing.T) { + t.Parallel() + + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + header := &block.Header{ + Epoch: 3, + Round: 7, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + + //make epochChangeConfirmed true (sp.epochStartTrigger.EpochStartRound() <= sp.epochStartTrigger.EpochFinalityAttestingRound()) + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 4 + }, + EpochStartRoundCalled: func() uint64 { + return 5 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + IsEpochStartCalled: func() bool { + return true + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with epoch 3 should be in epoch 4", err.Error()) +} + +func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsNotEpochStartButShouldBe(t *testing.T) { + t.Parallel() + + //isNotEpochStartButShouldBe := header.GetEpoch() != currentBlockHeader.GetEpoch() && !header.IsStartOfEpochBlock() + gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) + + header := &block.Header{ + Epoch: 4, + } + currentHeader := &block.Header{ + Epoch: 3, + } + blockChain := &testscommon.ChainHandlerStub{ + GetCurrentBlockHeaderCalled: func() data.HeaderHandler { + return currentHeader + }, + } + + epochStartTriggerStub := &mock.EpochStartTriggerStub{ + MetaEpochCalled: func() uint32 { + return 4 + }, + EpochStartRoundCalled: func() uint64 { + return 5 + }, + EpochFinalityAttestingRoundCalled: func() uint64 { + return 5 + }, + } + enableEpochsHandler := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return false + }, + } + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockChain": blockChain, + "epochStartTrigger": epochStartTriggerStub, + "enableEpochsHandler": enableEpochsHandler, + "epochChangeGracePeriodHandler": gracePeriod, + }) + require.Nil(t, err) + + err = sp.CheckEpochCorrectness(header) + assert.Error(t, err) + assert.Equal(t, "epoch does not match proposed header with new epoch 4 is not of type epoch start", err.Error()) +} + func TestShardProcessor_CreateNewHeaderErrWrongTypeAssertion(t *testing.T) { t.Parallel() From e5881551248a7bdacc04eed2f2f76634bbf8c2c6 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 23 Oct 2025 14:38:47 +0300 Subject: [PATCH 11/16] MX-17245 Added coverage for VerufyProposalBlock --- process/block/shardblock.go | 3 +- process/block/shardblockProposal_test.go | 125 ++++++++++++++++++++++- process/block/shardblock_test.go | 97 +++++++++++++++++- 3 files changed, 222 insertions(+), 3 deletions(-) diff --git a/process/block/shardblock.go b/process/block/shardblock.go index f692052039c..fe11eaefaee 100644 --- a/process/block/shardblock.go +++ b/process/block/shardblock.go @@ -1805,7 +1805,8 @@ func (sp *shardProcessor) getAllMiniBlockDstMeFromMeta(header data.ShardHeaderHa for _, metaBlockHash := range header.GetMetaBlockHashes() { headerInfo, ok := sp.hdrsForCurrBlock.GetHeaderInfo(string(metaBlockHash)) if !ok { - return nil, err + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromMeta metaBlockHash = %s", + process.ErrMissingHeader, logger.DisplayByteSlice(metaBlockHash)) } err = sp.addCrossShardMiniBlocksDstMeToMap(header, metaBlockHash, headerInfo.GetHeader(), lastCrossNotarizedHeader, miniBlockMetaHashes) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 0f51b3d146f..32761389a99 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -214,7 +214,7 @@ func TestShardProcessor_CreateBlockProposal(t *testing.T) { }) t.Run("selectIncomingMiniBlocksForProposal fails due to error on selectIncomingMiniBlocks", func(t *testing.T) { t.Parallel() - + expectedError := errors.New("expected error") coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() headers := dataComponents.DataPool.Headers() dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ @@ -1408,6 +1408,80 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "epoch does not match") }) + t.Run("checkMetaHeadersValidityAndFinalityProposal fails should error", func(t *testing.T) { + t.Parallel() + + expectedErr = errors.New("expected error from checkMetaHeadersValidityAndFinalityProposal") + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["blockTracker"] = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, expectedErr + }, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + assert.Equal(t, expectedErr, err) + }) + t.Run("verifyCrossShardMiniBlockDstMe fails should error", func(t *testing.T) { + t.Parallel() + + headerValidator := &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + subcomponents := createSubComponentsForVerifyProposalTest() + subcomponents["headerValidator"] = headerValidator + subcomponents["hasher"] = &hashingMocks.HasherMock{} + subcomponents["proofsPool"] = &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(subcomponents) + require.Nil(t, err) + + body := &block.Body{} + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + + err = sp.VerifyBlockProposal(header, body, haveTime) + + require.Error(t, err) + // stub will not return meta headers, causing type assertion to fail + expectedErr = errors.New("wrong type assertion") + assert.Equal(t, expectedErr, err) + }) t.Run("should work", func(t *testing.T) { t.Parallel() @@ -1632,6 +1706,55 @@ func TestShardProcessor_CheckMetaHeadersValidityAndFinalityProposal(t *testing.T require.ErrorContains(t, err, process.ErrHeaderNotFinal.Error()) }) + t.Run("GetHeaderByHash error should propagate", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + + metaHeader := &block.MetaBlockV3{} + + arguments.HeaderValidator = &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + + arguments.BlockTracker = &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return metaHeader, []byte("h"), nil + }, + } + + dataPool, ok := dataComponents.Datapool().(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + + expectedError := errors.New("expected error from GetHeaderByHash") + dataPool.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return nil, expectedError + }, + } + } + dataPool.ProofsCalled = func() retriever.ProofsPool { + return &dataRetriever.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + } + } + + sp, _ := blproc.NewShardProcessor(arguments) + + header := &block.HeaderV3{ + MetaBlockHashes: [][]byte{[]byte("hh")}, + } + err := sp.CheckMetaHeadersValidityAndFinalityProposal(header) + require.Error(t, err) + require.ErrorIs(t, err, expectedError) + }) + t.Run("should work", func(t *testing.T) { t.Parallel() diff --git a/process/block/shardblock_test.go b/process/block/shardblock_test.go index 2edc3599322..1bf64346c8c 100644 --- a/process/block/shardblock_test.go +++ b/process/block/shardblock_test.go @@ -25,6 +25,7 @@ import ( "github.com/multiversx/mx-chain-go/common/graceperiod" "github.com/multiversx/mx-chain-go/testscommon/cache" "github.com/multiversx/mx-chain-go/testscommon/pool" + "github.com/multiversx/mx-chain-go/testscommon/processMocks" vmcommon "github.com/multiversx/mx-chain-vm-common-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -4341,6 +4342,100 @@ func TestShardProcessor_RestoreMetaBlockIntoPoolShouldPass(t *testing.T) { assert.Nil(t, err) } +func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaGetLastCrossNotarizedHeaderErrorShouldError(t *testing.T) { + t.Parallel() + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return nil, nil, process.ErrNilBlockHeader + }, + }, + }) + require.Nil(t, err) + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + _, err = sp.GetAllMiniBlockDstMeFromMeta(header) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrNilBlockHeader) +} + +func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaMissingMetaHeaderShouldError(t *testing.T) { + t.Parallel() + + headerValidator := &processMocks.HeaderValidatorMock{ + IsHeaderConstructionValidCalled: func(currHdr, prevHdr data.HeaderHandler) error { + return nil + }, + } + poolMock := initDataPool() + poolMock.HeadersCalled = func() dataRetriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + //body := &block.Body{} + + metablockHashes := [][]byte{ + []byte("hash1"), + []byte("hash2"), + } + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + MetaBlockHashes: metablockHashes, + } + + sp, err := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ + "headerValidator": headerValidator, + "hasher": &hashingMocks.HasherMock{}, + "proofsPool": &dataRetrieverMock.ProofsPoolMock{ + HasProofCalled: func(shardID uint32, headerHash []byte) bool { + return true + }, + }, + "dataPool": poolMock, + "blockTracker": &mock.BlockTrackerMock{ + GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { + return &block.MetaBlockV3{}, []byte("h"), nil + }, + }, + "hdrsForCurrBlock": &testscommon.HeadersForBlockMock{ + GetHeaderInfoCalled: func(hash string) (headerForBlock.HeaderInfo, bool) { + return nil, false + }, + }, + }) + require.Nil(t, err) + + mblocks, err := sp.GetAllMiniBlockDstMeFromMeta(header) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrMissingHeader) + assert.Nil(t, mblocks) +} func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaShouldPass(t *testing.T) { t.Parallel() @@ -6052,7 +6147,7 @@ func TestVerifyCrossShardMiniBlockDstMe(t *testing.T) { } err := sp.VerifyCrossShardMiniBlockDstMe(header) - require.Nil(t, err) + require.NotNil(t, err) }) t.Run("cannot find header in pool should error", func(t *testing.T) { From 7abc343ce16839b8e0fdcf992b1e704c2ff5af6a Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 23 Oct 2025 16:09:16 +0300 Subject: [PATCH 12/16] MX-17245 Fixed racing issues --- process/block/shardblockProposal_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 32761389a99..37939bcb1c9 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -1411,11 +1411,11 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { t.Run("checkMetaHeadersValidityAndFinalityProposal fails should error", func(t *testing.T) { t.Parallel() - expectedErr = errors.New("expected error from checkMetaHeadersValidityAndFinalityProposal") + expError := errors.New("expected error from checkMetaHeadersValidityAndFinalityProposal") subcomponents := createSubComponentsForVerifyProposalTest() subcomponents["blockTracker"] = &mock.BlockTrackerMock{ GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { - return nil, nil, expectedErr + return nil, nil, expError }, } @@ -1436,7 +1436,7 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { err = sp.VerifyBlockProposal(header, body, haveTime) require.Error(t, err) - assert.Equal(t, expectedErr, err) + assert.Equal(t, expError, err) }) t.Run("verifyCrossShardMiniBlockDstMe fails should error", func(t *testing.T) { t.Parallel() @@ -1479,8 +1479,8 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { require.Error(t, err) // stub will not return meta headers, causing type assertion to fail - expectedErr = errors.New("wrong type assertion") - assert.Equal(t, expectedErr, err) + expError := errors.New("wrong type assertion") + assert.Equal(t, expError, err) }) t.Run("should work", func(t *testing.T) { t.Parallel() From 368677266ff016037b67ec299175da19c26213ae Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 23 Oct 2025 20:47:06 +0300 Subject: [PATCH 13/16] MX-17245 Added coverage for VerifyBlockProposal --- process/block/shardblockProposal_test.go | 122 +++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 37939bcb1c9..85e9afb292e 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -1482,6 +1482,128 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { expError := errors.New("wrong type assertion") assert.Equal(t, expError, err) }) + t.Run("verifyGasLimit fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *estimator.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + arguments.GasComputation = &testscommon.GasComputationMock{ + CheckIncomingMiniBlocksCalled: func(miniBlocks []data.MiniBlockHeaderHandler, transactions map[string][]data.TransactionHandler) (int, int, error) { + return 0, 0, expectedError + }, + } + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + t.Run("getHeaderHash fails should error", func(t *testing.T) { + t.Parallel() + + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() + + coreComponents.IntMarsh = &mock.MarshalizerStub{ + MarshalCalled: func(obj interface{}) ([]byte, error) { + t.Log("called IntMarsh.Marshal on ", obj) + return nil, expectedErr + }, + } + + poolMock, ok := dataComponents.DataPool.(*dataRetriever.PoolsHolderStub) + require.True(t, ok) + poolMock.HeadersCalled = func() retriever.HeadersPool { + return &pool.HeadersPoolStub{ + GetHeaderByHashCalled: func(hash []byte) (data.HeaderHandler, error) { + return &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + }, nil + }, + } + } + currentBlockHeader := &block.HeaderV3{ + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + _ = dataComponents.BlockChain.SetCurrentBlockHeaderAndRootHash(currentBlockHeader, []byte("root")) + dataComponents.BlockChain.SetCurrentBlockHeaderHash([]byte("hash")) + arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents) + arguments.ExecutionResultsVerifier = &processMocks.ExecutionResultsVerifierMock{ + VerifyHeaderExecutionResultsCalled: func(header data.HeaderHandler) error { + return nil + }, + } + arguments.ExecutionResultsInclusionEstimator = &processMocks.InclusionEstimatorMock{ + DecideCalled: func(lastNotarised *estimator.LastExecutionResultForInclusion, pending []data.BaseExecutionResultHandler, currentHdrTsMs uint64) (allowed int) { + return 0 + }, + } + + sp, err := blproc.NewShardProcessor(arguments) + require.Nil(t, err) + + body := &block.Body{} + + header := &block.HeaderV3{ + PrevHash: []byte("hash"), + Nonce: 1, + Round: 2, + MiniBlockHeaders: []block.MiniBlockHeader{}, + LastExecutionResult: &block.ExecutionResultInfo{ + ExecutionResult: &block.BaseExecutionResult{}, + }, + } + err = sp.VerifyBlockProposal(header, body, haveTime) + require.Error(t, err) + require.Equal(t, expectedErr, err) + }) + t.Run("should work", func(t *testing.T) { t.Parallel() From 686c113b8914c9aa04597c645e31d1c61c9e9b21 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Thu, 23 Oct 2025 22:09:27 +0300 Subject: [PATCH 14/16] MX-17245 Updated Error messages for meta --- process/block/metablock.go | 15 ++++++++++----- process/block/metablock_test.go | 3 ++- process/block/shardblock.go | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/process/block/metablock.go b/process/block/metablock.go index 7df526c23f7..760a7933ffc 100644 --- a/process/block/metablock.go +++ b/process/block/metablock.go @@ -476,11 +476,13 @@ func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr *block.MetaBlock for _, shardInfo := range metaHdr.ShardInfo { headerInfo, ok := mp.hdrsForCurrBlock.GetHeaderInfo(string(shardInfo.HeaderHash)) if !ok { - continue + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + process.ErrMissingHeader, hex.EncodeToString(shardInfo.HeaderHash)) } shardHeader, ok := headerInfo.GetHeader().(data.ShardHeaderHandler) if !ok { - continue + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + process.ErrWrongTypeAssertion, hex.EncodeToString(shardInfo.HeaderHash)) } lastCrossNotarizedHeader, _, err := mp.blockTracker.GetLastCrossNotarizedHeader(shardInfo.ShardID) @@ -489,13 +491,16 @@ func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr *block.MetaBlock } if shardHeader.GetRound() > metaHdr.Round { - continue + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + process.ErrHigherRoundInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } if shardHeader.GetRound() <= lastCrossNotarizedHeader.GetRound() { - continue + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + process.ErrLowerRoundInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } if shardHeader.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { - continue + return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + process.ErrLowerNonceInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } finalCrossMiniBlockHashes := mp.getFinalCrossMiniBlockHashes(shardHeader) diff --git a/process/block/metablock_test.go b/process/block/metablock_test.go index b0320f68793..036715ae4da 100644 --- a/process/block/metablock_test.go +++ b/process/block/metablock_test.go @@ -2791,7 +2791,8 @@ func TestMetaProcessor_VerifyCrossShardMiniBlocksDstMe(t *testing.T) { assert.Nil(t, err) err = mp.VerifyCrossShardMiniBlockDstMe(hdr) - assert.Nil(t, err) + assert.Error(t, err) + assert.ErrorIs(t, err, process.ErrMissingHeader) } func TestMetaProcess_CreateNewBlockHeaderProcessHeaderExpectCheckRoundCalled(t *testing.T) { diff --git a/process/block/shardblock.go b/process/block/shardblock.go index 2d00a9e95d0..ab86336db76 100644 --- a/process/block/shardblock.go +++ b/process/block/shardblock.go @@ -1853,7 +1853,7 @@ func (sp *shardProcessor) getAllMiniBlockDstMeFromMeta(header data.ShardHeaderHa headerInfo, ok := sp.hdrsForCurrBlock.GetHeaderInfo(string(metaBlockHash)) if !ok { return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromMeta metaBlockHash = %s", - process.ErrMissingHeader, logger.DisplayByteSlice(metaBlockHash)) + process.ErrMissingHeader, hex.EncodeToString(metaBlockHash)) } err = sp.addCrossShardMiniBlocksDstMeToMap(header, metaBlockHash, headerInfo.GetHeader(), lastCrossNotarizedHeader, miniBlockMetaHashes) From 18dd5a0c5eb7f83c0572c3f76696338d8b3bc093 Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Mon, 27 Oct 2025 13:39:12 +0200 Subject: [PATCH 15/16] MX-17245 Fixes after review --- process/block/export_test.go | 51 ++------------ process/block/metablock.go | 10 +-- process/block/shardblock.go | 2 +- process/block/shardblockProposal_test.go | 37 ++++++---- process/block/shardblock_test.go | 16 +---- process/coordinator/processProposal.go | 2 +- .../factory/genericPartialComponent.go | 67 +++++++++++++++++++ .../processMocks/gracePeriodErrStub.go | 13 ++++ 8 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 testscommon/factory/genericPartialComponent.go create mode 100644 testscommon/processMocks/gracePeriodErrStub.go diff --git a/process/block/export_test.go b/process/block/export_test.go index 4c289ad7542..cb16e8fb727 100644 --- a/process/block/export_test.go +++ b/process/block/export_test.go @@ -1,11 +1,8 @@ package block import ( - "fmt" - "reflect" "sync" "time" - "unsafe" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" @@ -799,6 +796,7 @@ func (sp *shardProcessor) CollectExecutionResults(headerHash []byte, header data return sp.collectExecutionResults(headerHash, header, body) } +// AddExecutionResultsOnHeader - func (sp *shardProcessor) AddExecutionResultsOnHeader(shardHeader data.HeaderHandler) error { return sp.addExecutionResultsOnHeader(shardHeader) } @@ -828,47 +826,12 @@ func GetHaveTimeForProposal(startTime time.Time, maxDuration time.Duration) func return getHaveTimeForProposal(startTime, maxDuration) } +// ConstructPartialShardBlockProcessorForTest - func ConstructPartialShardBlockProcessorForTest(subcomponents map[string]interface{}) (*shardProcessor, error) { - bp := &baseProcessor{} - sp := &shardProcessor{baseProcessor: bp} - - setField := func(target any, name string, component any) error { - rv := reflect.ValueOf(target).Elem() - field := rv.FieldByName(name) - if !field.IsValid() { - return fmt.Errorf("invalid field: %s", name) - } - if !field.CanSet() { - // bypass export check (ok in tests, same package) - field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() - } - - val := reflect.ValueOf(component) - switch { - case val.Type().AssignableTo(field.Type()): - field.Set(val) - case val.Type().ConvertibleTo(field.Type()): - field.Set(val.Convert(field.Type())) - case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().AssignableTo(field.Type().Elem()): - ptr := reflect.New(val.Type()) - ptr.Elem().Set(val) - field.Set(ptr) - case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().ConvertibleTo(field.Type().Elem()): - ptr := reflect.New(field.Type().Elem()) - ptr.Elem().Set(val.Convert(field.Type().Elem())) - field.Set(ptr) - default: - return fmt.Errorf("cannot set field %s (got %s, expected %s)", name, val.Type(), field.Type()) - } - return nil - } - - for name, component := range subcomponents { - if err := setField(sp, name, component); err != nil { - if err2 := setField(sp.baseProcessor, name, component); err2 != nil { - return nil, err - } - } + sp := &shardProcessor{} + err := factory.ConstructPartialComponentForTest(sp, subcomponents) + if err != nil { + return nil, err } - return sp, nil + return sp, err } diff --git a/process/block/metablock.go b/process/block/metablock.go index 760a7933ffc..0fedd574dda 100644 --- a/process/block/metablock.go +++ b/process/block/metablock.go @@ -476,12 +476,12 @@ func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr *block.MetaBlock for _, shardInfo := range metaHdr.ShardInfo { headerInfo, ok := mp.hdrsForCurrBlock.GetHeaderInfo(string(shardInfo.HeaderHash)) if !ok { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + return nil, fmt.Errorf("%w for shard info with hash = %s", process.ErrMissingHeader, hex.EncodeToString(shardInfo.HeaderHash)) } shardHeader, ok := headerInfo.GetHeader().(data.ShardHeaderHandler) if !ok { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + return nil, fmt.Errorf("%w : for shardInfo.HeaderHash = %s", process.ErrWrongTypeAssertion, hex.EncodeToString(shardInfo.HeaderHash)) } @@ -491,15 +491,15 @@ func (mp *metaProcessor) getAllMiniBlockDstMeFromShards(metaHdr *block.MetaBlock } if shardHeader.GetRound() > metaHdr.Round { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + return nil, fmt.Errorf("%w : for shard info with hash = %s", process.ErrHigherRoundInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } if shardHeader.GetRound() <= lastCrossNotarizedHeader.GetRound() { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + return nil, fmt.Errorf("%w : for shard info with hash = %s", process.ErrLowerRoundInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } if shardHeader.GetNonce() <= lastCrossNotarizedHeader.GetNonce() { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromShards shardInfo.HeaderHash = %s", + return nil, fmt.Errorf("%w : for shard info with hash = %s", process.ErrLowerNonceInBlock, hex.EncodeToString(shardInfo.HeaderHash)) } diff --git a/process/block/shardblock.go b/process/block/shardblock.go index ab86336db76..b5628c11a05 100644 --- a/process/block/shardblock.go +++ b/process/block/shardblock.go @@ -1852,7 +1852,7 @@ func (sp *shardProcessor) getAllMiniBlockDstMeFromMeta(header data.ShardHeaderHa for _, metaBlockHash := range header.GetMetaBlockHashes() { headerInfo, ok := sp.hdrsForCurrBlock.GetHeaderInfo(string(metaBlockHash)) if !ok { - return nil, fmt.Errorf("%w : getAllMiniBlockDstMeFromMeta metaBlockHash = %s", + return nil, fmt.Errorf("%w for metaBlockHash %s", process.ErrMissingHeader, hex.EncodeToString(metaBlockHash)) } diff --git a/process/block/shardblockProposal_test.go b/process/block/shardblockProposal_test.go index 85e9afb292e..0fefc4d6bc5 100644 --- a/process/block/shardblockProposal_test.go +++ b/process/block/shardblockProposal_test.go @@ -214,7 +214,7 @@ func TestShardProcessor_CreateBlockProposal(t *testing.T) { }) t.Run("selectIncomingMiniBlocksForProposal fails due to error on selectIncomingMiniBlocks", func(t *testing.T) { t.Parallel() - expectedError := errors.New("expected error") + coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks() headers := dataComponents.DataPool.Headers() dataComponents.DataPool = &dataRetriever.PoolsHolderStub{ @@ -620,7 +620,7 @@ func Test_addExecutionResultsOnHeader(t *testing.T) { t.Parallel() t.Run("executionResultsTracker returns error should error", func(t *testing.T) { t.Parallel() - expectedErr := errors.New("expected error") + sp, _ := blproc.ConstructPartialShardBlockProcessorForTest(map[string]interface{}{ "executionResultsTracker": &testscommonExecutionTrack.ExecutionResultsTrackerStub{ GetPendingExecutionResultsCalled: func() ([]data.BaseExecutionResultHandler, error) { @@ -1348,7 +1348,7 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { t.Parallel() subcomponents := createSubComponentsForVerifyProposalTest() - subcomponents["epochChangeGracePeriodHandler"] = &gracePeriodErrStub{} + subcomponents["epochChangeGracePeriodHandler"] = processMocks.GracePeriodErrStub{} subcomponents["epochStartTrigger"] = &mock.EpochStartTriggerStub{ EpochStartRoundCalled: func() uint64 { return 10 @@ -1411,11 +1411,10 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { t.Run("checkMetaHeadersValidityAndFinalityProposal fails should error", func(t *testing.T) { t.Parallel() - expError := errors.New("expected error from checkMetaHeadersValidityAndFinalityProposal") subcomponents := createSubComponentsForVerifyProposalTest() subcomponents["blockTracker"] = &mock.BlockTrackerMock{ GetLastCrossNotarizedHeaderCalled: func(shardID uint32) (data.HeaderHandler, []byte, error) { - return nil, nil, expError + return nil, nil, expectedError }, } @@ -1436,7 +1435,7 @@ func TestShardProcessor_VerifyBlockProposal(t *testing.T) { err = sp.VerifyBlockProposal(header, body, haveTime) require.Error(t, err) - assert.Equal(t, expError, err) + assert.Equal(t, expectedError, err) }) t.Run("verifyCrossShardMiniBlockDstMe fails should error", func(t *testing.T) { t.Parallel() @@ -3146,24 +3145,34 @@ func TestShardProcessor_collectExecutionResults(t *testing.T) { actual_miniblocks[block.TxBlock]++ if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(0) { assert.Equal(t, uint32(2), mbResult.GetTxCount(), "same-shard tx miniblock should have 2 txs as per mock") - } else if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { + continue + } + if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { assert.Equal(t, uint32(2), mbResult.GetTxCount(), "cross-shard tx miniblock should have 2 txs as per mock") - } else if mbResult.GetSenderShardID() == uint32(1) && mbResult.GetReceiverShardID() == uint32(0) { + continue + } + if mbResult.GetSenderShardID() == uint32(1) && mbResult.GetReceiverShardID() == uint32(0) { assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") - } else if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + continue + } + if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { assert.Equal(t, uint32(1), mbResult.GetTxCount(), "cross-shard tx miniblock should have 1 tx as per mock") - } else { - require.Fail(t, "unexpected tx miniblock shard IDs") + continue } + require.Fail(t, "unexpected tx miniblock shard IDs") + case block.SmartContractResultBlock: actual_miniblocks[block.SmartContractResultBlock]++ if mbResult.GetSenderShardID() == uint32(0) && mbResult.GetReceiverShardID() == uint32(1) { assert.Equal(t, uint32(1), mbResult.GetTxCount(), "outgoing SCR miniblock should have 1 tx as per mock") - } else if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { + continue + } + if mbResult.GetSenderShardID() == uint32(2) && mbResult.GetReceiverShardID() == uint32(0) { assert.Equal(t, uint32(2), mbResult.GetTxCount(), "incoming SCR miniblock should have 2 txs as per mock") - } else { - require.Fail(t, "unexpected SCR miniblock shard IDs") + continue } + require.Fail(t, "unexpected SCR miniblock shard IDs") + case block.ReceiptBlock: actual_miniblocks[block.ReceiptBlock]++ assert.NotEqual(t, uint32(0), mbResult.GetSenderShardID(), "receipt miniblock should not be self-shard") diff --git a/process/block/shardblock_test.go b/process/block/shardblock_test.go index 5b2bd634efb..57cc9afec11 100644 --- a/process/block/shardblock_test.go +++ b/process/block/shardblock_test.go @@ -4392,7 +4392,6 @@ func TestShardPreprocessor_getAllMiniBlockDstMeFromMetaMissingMetaHeaderShouldEr }, } } - //body := &block.Body{} metablockHashes := [][]byte{ []byte("hash1"), @@ -4849,19 +4848,11 @@ func TestShardProcessor_updateStateStorage(t *testing.T) { assert.True(t, cancelPruneWasCalled) } -type gracePeriodErrStub struct{} - -func (gracePeriodErrStub) GetGracePeriodForEpoch(_ uint32) (uint32, error) { - return 0, errors.New("epochChangeGracePeriodHandler forced error") -} - -func (gracePeriodErrStub) IsInterfaceNil() bool { return false } - func TestShardProcessor_checkEpochCorrectnessCrossChain_gracePeriodError(t *testing.T) { t.Parallel() genesisNonce := uint64(0) - gracePeriod := &gracePeriodErrStub{} + gracePeriod := &processMocks.GracePeriodErrStub{} blockchain := &testscommon.ChainHandlerStub{ GetCurrentBlockHeaderCalled: func() data.HeaderHandler { return &block.Header{ @@ -5475,7 +5466,6 @@ func TestShardProcessor_CheckEpochCorrectnessShouldRemoveAndRequestStartOfEpochM func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsHeaderOfInvalidEpoch(t *testing.T) { t.Parallel() - // isHeaderOfInvalidEpoch := header.GetEpoch() > sp.epochStartTrigger.MetaEpoch() header := &block.Header{ Epoch: 3, } @@ -5531,7 +5521,7 @@ func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenEpochChangeGracePeri "blockChain": blockChain, "epochStartTrigger": epochStartTriggerStub, "enableEpochsHandler": enableEpochsHandler, - "epochChangeGracePeriodHandler": &gracePeriodErrStub{}, + "epochChangeGracePeriodHandler": &processMocks.GracePeriodErrStub{}, }) require.Nil(t, err) @@ -5558,7 +5548,6 @@ func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsOldEpochAndShouldB }, } - //make epochChangeConfirmed true (sp.epochStartTrigger.EpochStartRound() <= sp.epochStartTrigger.EpochFinalityAttestingRound()) epochStartTriggerStub := &mock.EpochStartTriggerStub{ MetaEpochCalled: func() uint32 { return 4 @@ -5594,7 +5583,6 @@ func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsOldEpochAndShouldB func TestShardProcessor_CheckEpochCorrectnessShouldErrorWhenIsNotEpochStartButShouldBe(t *testing.T) { t.Parallel() - //isNotEpochStartButShouldBe := header.GetEpoch() != currentBlockHeader.GetEpoch() && !header.IsStartOfEpochBlock() gracePeriod, _ := graceperiod.NewEpochChangeGracePeriod([]config.EpochChangeGracePeriodByEpoch{{EnableEpoch: 0, GracePeriodInRounds: 1}}) header := &block.Header{ diff --git a/process/coordinator/processProposal.go b/process/coordinator/processProposal.go index cd6c65ce0e3..da05d8dd230 100644 --- a/process/coordinator/processProposal.go +++ b/process/coordinator/processProposal.go @@ -134,7 +134,7 @@ func (tc *transactionCoordinator) CreateMbsCrossShardDstMe( // if not all mini blocks were included, remove them from the miniBlocksAndHashes slice // but add them into pendingMiniBlocksAndHashes - if lastMBIndex+1 < len(mbsSlice) { + if lastMBIndex < len(mbsSlice)-1 { pendingMiniBlocksAndHashes = miniBlocksAndHashes[lastMBIndex+1:] miniBlocksAndHashes = miniBlocksAndHashes[:lastMBIndex+1] } diff --git a/testscommon/factory/genericPartialComponent.go b/testscommon/factory/genericPartialComponent.go new file mode 100644 index 00000000000..66dc1df1abe --- /dev/null +++ b/testscommon/factory/genericPartialComponent.go @@ -0,0 +1,67 @@ +package factory + +import ( + "fmt" + "reflect" + "unsafe" +) + +// ConstructPartialComponentForTest initializes the given component by setting its subcomponents +func ConstructPartialComponentForTest(component interface{}, subcomponents map[string]interface{}) error { + + // initialize nil pointer fields + rv := reflect.ValueOf(component).Elem() + for i := 0; i < rv.NumField(); i++ { + field := rv.Field(i) + ft := rv.Type().Field(i) + + // handle embedded *structs that are nil + if ft.Anonymous && field.Kind() == reflect.Ptr && field.IsNil() && field.Type().Elem().Kind() == reflect.Struct { + newVal := reflect.New(field.Type().Elem()) + if !field.CanSet() { + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + field.Set(newVal) + } + } + + // set subcomponents + setField := func(target any, name string, component any) error { + rv := reflect.ValueOf(target).Elem() + field := rv.FieldByName(name) + if !field.IsValid() { + component = nil + return fmt.Errorf("invalid field: %s", name) + } + if !field.CanSet() { + // bypass export check (ok in tests, same package) + field = reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem() + } + + val := reflect.ValueOf(component) + switch { + case val.Type().AssignableTo(field.Type()): + field.Set(val) + case val.Type().ConvertibleTo(field.Type()): + field.Set(val.Convert(field.Type())) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().AssignableTo(field.Type().Elem()): + ptr := reflect.New(val.Type()) + ptr.Elem().Set(val) + field.Set(ptr) + case val.Kind() != reflect.Ptr && field.Kind() == reflect.Ptr && val.Type().ConvertibleTo(field.Type().Elem()): + ptr := reflect.New(field.Type().Elem()) + ptr.Elem().Set(val.Convert(field.Type().Elem())) + field.Set(ptr) + default: + return fmt.Errorf("cannot set field %s (got %s, expected %s)", name, val.Type(), field.Type()) + } + return nil + } + + for name, subComponent := range subcomponents { + if err := setField(component, name, subComponent); err != nil { + return err + } + } + return nil +} diff --git a/testscommon/processMocks/gracePeriodErrStub.go b/testscommon/processMocks/gracePeriodErrStub.go new file mode 100644 index 00000000000..79627f99fb1 --- /dev/null +++ b/testscommon/processMocks/gracePeriodErrStub.go @@ -0,0 +1,13 @@ +package processMocks + +import "errors" + +type GracePeriodErrStub struct{} + +// GetGracePeriodForEpoch always returns an error. +func (GracePeriodErrStub) GetGracePeriodForEpoch(_ uint32) (uint32, error) { + return 0, errors.New("epochChangeGracePeriodHandler forced error") +} + +// IsInterfaceNil - +func (GracePeriodErrStub) IsInterfaceNil() bool { return false } From ebb9c58675b688cd9aa686ec5dc4d94d7060fb1b Mon Sep 17 00:00:00 2001 From: Mihaela Radian Date: Mon, 27 Oct 2025 13:46:00 +0200 Subject: [PATCH 16/16] MX-17245 Fixes --- testscommon/factory/genericPartialComponent.go | 1 - 1 file changed, 1 deletion(-) diff --git a/testscommon/factory/genericPartialComponent.go b/testscommon/factory/genericPartialComponent.go index 66dc1df1abe..20be6e9db62 100644 --- a/testscommon/factory/genericPartialComponent.go +++ b/testscommon/factory/genericPartialComponent.go @@ -30,7 +30,6 @@ func ConstructPartialComponentForTest(component interface{}, subcomponents map[s rv := reflect.ValueOf(target).Elem() field := rv.FieldByName(name) if !field.IsValid() { - component = nil return fmt.Errorf("invalid field: %s", name) } if !field.CanSet() {