Skip to content
109 changes: 109 additions & 0 deletions node/chainSimulator/chainSimulator_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package chainSimulator

import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
"strings"
Expand All @@ -11,6 +13,7 @@ import (
apiBlock "github.com/multiversx/mx-chain-core-go/data/api"
"github.com/multiversx/mx-chain-core-go/data/block"
"github.com/multiversx/mx-chain-core-go/data/transaction"
"github.com/multiversx/mx-chain-go/node/external/transactionAPI"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -755,6 +758,112 @@ func TestSimulator_SendMoveBalanceTxBeforeAndAfterSupernovaWithMoreGasLimit(t *t
chainSimulatorCommon.GenerateMoveBalanceTxsInShardsWithMoreGasLimit(t, chainSimulator)
}

// TestRemoveSCRFromPoolAndDestinationShouldBeRequested checks that, after an
// ESDT issue SCR is manually removed from the pools, the destination shard
// requests it again and the SCR becomes available through the API.
func TestRemoveSCRFromPoolAndDestinationShouldBeRequested(t *testing.T) {
activationEpoch := uint32(4)

baseIssuingCost := "1000"

cs, err := NewChainSimulator(ArgsChainSimulator{
BypassTxSignatureCheck: true,
BypassCreateBlockTimeCheck: true,
TempDir: t.TempDir(),
PathToInitialConfig: defaultPathToInitialConfig,
NumOfShards: defaultNumOfShards,
RoundDurationInMillis: defaultRoundDurationInMillis,
SupernovaRoundDurationInMillis: defaultSupernovaRoundDurationInMillis,
RoundsPerEpoch: defaultRoundsPerEpoch,
SupernovaRoundsPerEpoch: defaultSupernovaRoundsPerEpoch,
ApiInterface: api.NewNoApiInterface(),
MinNodesPerShard: defaultMinNodesPerShard,
MetaChainMinNodes: defaultMetaChainMinNodes,
AlterConfigsFunction: func(cfg *config.Configs) {
cfg.EpochConfig.EnableEpochs.StakingV2EnableEpoch = 0
cfg.SystemSCConfig.ESDTSystemSCConfig.BaseIssuingCost = baseIssuingCost
cfg.EpochConfig.EnableEpochs.SupernovaEnableEpoch = uint32(2)
cfg.RoundConfig.RoundActivations[string(common.SupernovaRoundFlag)] = config.ActivationRoundByName{
Round: "46",
}

},
})
require.Nil(t, err)
require.NotNil(t, cs)

defer cs.Close()

wallet0, err := cs.GenerateAndMintWalletAddress(0, chainSimulatorCommon.OneEGLD)
require.Nil(t, err)

err = cs.GenerateBlocksUntilEpochIsReached(int32(activationEpoch))
require.Nil(t, err)

nftTicker := []byte("NFTTICKER")
nonce := uint64(0)

callValue, _ := big.NewInt(0).SetString(baseIssuingCost, 10)

txDataField := bytes.Join(
[][]byte{
[]byte("issueNonFungible"),
[]byte(hex.EncodeToString([]byte("asdname"))),
[]byte(hex.EncodeToString(nftTicker)),
},
[]byte("@"),
)

tx := &transaction.Transaction{
Nonce: nonce,
SndAddr: wallet0.Bytes,
RcvAddr: core.ESDTSCAddress,
GasLimit: 100_000_000,
GasPrice: 1_000_000_000,
Signature: []byte("dummySig"),
Data: txDataField,
Value: callValue,
ChainID: []byte(configs.ChainID),
Version: 1,
}

txResult, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, 10)
require.Nil(t, err)
require.NotNil(t, txResult)
require.Equal(t, "success", txResult.Status.String())

// SCRS remove from pool
keys := cs.GetNodeHandler(core.MetachainShardId).GetDataComponents().Datapool().UnsignedTransactions().Keys()
scrsForShardZero := cs.GetNodeHandler(0).GetDataComponents().Datapool().UnsignedTransactions().Keys()
for _, key := range keys {
cs.GetNodeHandler(core.MetachainShardId).GetDataComponents().Datapool().UnsignedTransactions().RemoveDataFromAllShards(key)
cs.GetNodeHandler(0).GetDataComponents().Datapool().UnsignedTransactions().RemoveDataFromAllShards(key)
}

scrHash := scrsForShardZero[0]
res, err := cs.GetNodeHandler(0).GetFacadeHandler().GetTransaction(hex.EncodeToString(scrHash), true)
require.Nil(t, res)
require.True(t, strings.Contains(err.Error(), transactionAPI.ErrTransactionNotFound.Error()))

called := false
count := 0
for {
count++
err = cs.GenerateBlocks(1)
require.Nil(t, err)

res, _ = cs.GetNodeHandler(0).GetFacadeHandler().GetTransaction(hex.EncodeToString(scrHash), true)
if res != nil {
called = true
break
}
if count == 100 {
require.FailNow(t, "cannot find SCR on the destination shard")
}
}
require.True(t, called)
}

func TestChainSimulator_VerifyEconomicsMetricsSupernova(t *testing.T) {
if testing.Short() {
t.Skip("this is not a short test")
Expand Down
22 changes: 22 additions & 0 deletions process/block/baseProcess.go
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,28 @@ func (bp *baseProcessor) checkHeaderBodyCorrelation(miniBlockHeaders []data.Mini
delete(mbHashesFromHdr, mbHashStr)
}

err = checkForDuplicatedTxHashes(body)
if err != nil {
return err
}

return nil
}

func checkForDuplicatedTxHashes(body *block.Body) error {
txHashesSeen := make(map[string]struct{})
for _, miniBlock := range body.MiniBlocks {
if miniBlock == nil {
continue
}
for _, txHash := range miniBlock.TxHashes {
txHashStr := string(txHash)
if _, ok := txHashesSeen[txHashStr]; ok {
return process.ErrDuplicatedTransactionInBlockBody
}
txHashesSeen[txHashStr] = struct{}{}
}
}
return nil
}
Comment on lines +1307 to +1322

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check is already done in another function


Expand Down
184 changes: 184 additions & 0 deletions process/block/baseProcess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4592,6 +4592,190 @@ func TestCheckHeaderBodyCorrelationProposal(t *testing.T) {
)
require.NoError(t, err)
})

t.Run("duplicate tx hash across miniblocks with proposal should error", func(t *testing.T) {
coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks()
coreComponents.Hash = &hashingMocks.HasherMock{}
bootstrapComponents.Coordinator, _ = sharding.NewMultiShardCoordinator(3, 0)
arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents)
bp, _ := blproc.NewShardProcessor(arguments)

miniBlock1 := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 1,
TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")},
Type: block.TxBlock,
Reserved: nil,
}
miniBlock2 := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 2,
TxHashes: [][]byte{[]byte("tx1")},
Type: block.TxBlock,
Reserved: nil,
}

mbHash1, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock1)
mbHash2, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock2)

mbHeaders := make([]data.MiniBlockHeaderHandler, 2)
mbHeaders[0] = &block.MiniBlockHeader{
Hash: mbHash1,
SenderShardID: 0,
ReceiverShardID: 1,
TxCount: 2,
Type: block.TxBlock,
Reserved: nil,
}
_ = mbHeaders[0].SetConstructionState(int32(block.Proposed))
_ = mbHeaders[0].SetProcessingType(int32(block.Normal))
mbHeaders[1] = &block.MiniBlockHeader{
Hash: mbHash2,
SenderShardID: 0,
ReceiverShardID: 2,
TxCount: 1,
Type: block.TxBlock,
Reserved: nil,
}
_ = mbHeaders[1].SetConstructionState(int32(block.Proposed))
_ = mbHeaders[1].SetProcessingType(int32(block.Normal))

err := bp.CheckHeaderBodyCorrelationProposal(
mbHeaders,
&block.Body{MiniBlocks: []*block.MiniBlock{miniBlock1, miniBlock2}},
shardID,
)
require.Equal(t, process.ErrDuplicatedTransactionInBlockBody, err)
})

t.Run("duplicate tx hash within single miniblock with proposal should error", func(t *testing.T) {
coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks()
coreComponents.Hash = &hashingMocks.HasherMock{}
bootstrapComponents.Coordinator, _ = sharding.NewMultiShardCoordinator(3, 0)
arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents)
bp, _ := blproc.NewShardProcessor(arguments)

miniBlock := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 1,
TxHashes: [][]byte{[]byte("tx1"), []byte("tx1")},
Type: block.TxBlock,
Reserved: nil,
}

mbHash, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock)

mbHeaders := make([]data.MiniBlockHeaderHandler, 1)
mbHeaders[0] = &block.MiniBlockHeader{
Hash: mbHash,
SenderShardID: 0,
ReceiverShardID: 1,
TxCount: 2,
Type: block.TxBlock,
Reserved: nil,
}
_ = mbHeaders[0].SetConstructionState(int32(block.Proposed))
_ = mbHeaders[0].SetProcessingType(int32(block.Normal))

err := bp.CheckHeaderBodyCorrelationProposal(
mbHeaders,
&block.Body{MiniBlocks: []*block.MiniBlock{miniBlock}},
shardID,
)
require.Equal(t, process.ErrDuplicatedTransactionInBlockBody, err)
})

t.Run("duplicate tx hash across miniblocks without proposal should error", func(t *testing.T) {
coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks()
coreComponents.Hash = &hashingMocks.HasherMock{}
bootstrapComponents.Coordinator, _ = sharding.NewMultiShardCoordinator(3, 0)
arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents)
bp, _ := blproc.NewShardProcessor(arguments)

miniBlock1 := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 1,
TxHashes: [][]byte{[]byte("tx1"), []byte("tx2")},
Type: block.TxBlock,
Reserved: nil,
}
miniBlock2 := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 2,
TxHashes: [][]byte{[]byte("tx1")},
Type: block.TxBlock,
Reserved: nil,
}

mbHash1, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock1)
mbHash2, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock2)

hdr := &block.Header{
ShardID: shardID,
MiniBlockHeaders: []block.MiniBlockHeader{
{
Hash: mbHash1,
SenderShardID: 0,
ReceiverShardID: 1,
TxCount: 2,
Type: block.TxBlock,
Reserved: nil,
},
{
Hash: mbHash2,
SenderShardID: 0,
ReceiverShardID: 2,
TxCount: 1,
Type: block.TxBlock,
Reserved: nil,
},
},
}

err := bp.CheckHeaderBodyCorrelation(
hdr,
&block.Body{MiniBlocks: []*block.MiniBlock{miniBlock1, miniBlock2}},
)
require.Equal(t, process.ErrDuplicatedTransactionInBlockBody, err)
})

t.Run("duplicate tx hash within single miniblock without proposal should error", func(t *testing.T) {
coreComponents, dataComponents, bootstrapComponents, statusComponents := createComponentHolderMocks()
coreComponents.Hash = &hashingMocks.HasherMock{}
bootstrapComponents.Coordinator, _ = sharding.NewMultiShardCoordinator(3, 0)
arguments := CreateMockArguments(coreComponents, dataComponents, bootstrapComponents, statusComponents)
bp, _ := blproc.NewShardProcessor(arguments)

miniBlock := &block.MiniBlock{
SenderShardID: 0,
ReceiverShardID: 1,
TxHashes: [][]byte{[]byte("tx1"), []byte("tx1")},
Type: block.TxBlock,
Reserved: nil,
}

mbHash, _ := core.CalculateHash(coreComponents.IntMarsh, coreComponents.Hash, miniBlock)

hdr := &block.Header{
ShardID: shardID,
MiniBlockHeaders: []block.MiniBlockHeader{
{
Hash: mbHash,
SenderShardID: 0,
ReceiverShardID: 1,
TxCount: 2,
Type: block.TxBlock,
Reserved: nil,
},
},
}

err := bp.CheckHeaderBodyCorrelation(
hdr,
&block.Body{MiniBlocks: []*block.MiniBlock{miniBlock}},
)
require.Equal(t, process.ErrDuplicatedTransactionInBlockBody, err)
})
}

func TestBaseProcessor_GetFinalMiniBlocksFromExecutionResult(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions process/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -1395,6 +1395,9 @@ var ErrDuplicatedHashInBlock = errors.New("duplicated hash in block")
// ErrDoubleTransactionsFound signals that double transactions found
var ErrDoubleTransactionsFound = errors.New("double transactions found")

// ErrDuplicatedTransactionInBlockBody signals that a transaction hash appears more than once in the block body
var ErrDuplicatedTransactionInBlockBody = errors.New("duplicated transaction in block body")

// ErrPeerAlreadyAuthenticated signals that a peer authentication message was received for a peer that already has an existing mapping
var ErrPeerAlreadyAuthenticated = errors.New("peer already authenticated")

Expand Down
Loading