Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions integrationTests/chainSimulator/rewards/rewards_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package rewards

import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"path"
"testing"
"time"

"github.com/multiversx/mx-chain-core-go/core"
apiCore "github.com/multiversx/mx-chain-core-go/data/api"
"github.com/multiversx/mx-chain-core-go/data/block"
"github.com/multiversx/mx-chain-go/node/chainSimulator"
"github.com/multiversx/mx-chain-go/node/chainSimulator/components/api"
"github.com/multiversx/mx-chain-go/sharding/nodesCoordinator"
"github.com/stretchr/testify/require"
)

const (
defaultPathToInitialConfig = "../../../cmd/node/config/"
)

func TestRewardsTxsAfterEquivalentMessages(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we consider this a long test as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, changed

if testing.Short() {
t.Skip("this is not a short test")
}

startTime := time.Now().Unix()
roundDurationInMillis := uint64(6000)
roundsPerEpoch := core.OptionalUint64{
HasValue: true,
Value: 200,
}

numOfShards := uint32(3)

tempDir := t.TempDir()
cs, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{
BypassTxSignatureCheck: true,
TempDir: tempDir,
PathToInitialConfig: defaultPathToInitialConfig,
NumOfShards: numOfShards,
GenesisTimestamp: startTime,
RoundDurationInMillis: roundDurationInMillis,
RoundsPerEpoch: roundsPerEpoch,
ApiInterface: api.NewNoApiInterface(),
MinNodesPerShard: 3,
MetaChainMinNodes: 3,
})
require.Nil(t, err)
require.NotNil(t, cs)
defer cs.Close()

targetEpoch := 9
for i := 0; i < targetEpoch; i++ {
err = cs.ForceChangeOfEpoch()
require.Nil(t, err)
}

err = cs.GenerateBlocks(210)
require.Nil(t, err)

metaFacadeHandler := cs.GetNodeHandler(core.MetachainShardId).GetFacadeHandler()

nodesSetupFile := path.Join(tempDir, "config", "nodesSetup.json")
validators, err := readValidatorsAndOwners(nodesSetupFile)
require.Nil(t, err)

// find block with rewards transactions, in this range we should find the epoch start block
var metaBlock *apiCore.Block
found := false
for nonce := uint64(210); nonce < 235; nonce++ {
metaBlock, err = metaFacadeHandler.GetBlockByNonce(nonce, apiCore.BlockQueryOptions{
WithTransactions: true,
})
require.Nil(t, err)

isEpochStart := metaBlock.EpochStartInfo != nil
if !isEpochStart {
continue
}

found = true
break
}
require.True(t, found)
require.NotNil(t, metaBlock)

coordinator := cs.GetNodeHandler(0).GetProcessComponents().NodesCoordinator()

rewardsPerShard := computeRewardsForShards(metaBlock, coordinator, validators)

for shardID, reward := range rewardsPerShard {
fmt.Printf("rewards on shard %d: %s\n", shardID, reward.String())
}

require.True(t, allValuesEqual(rewardsPerShard))
}

func computeRewardsForShards(
metaBlock *apiCore.Block,
coordinator nodesCoordinator.NodesCoordinator,
validators map[string]string,
) map[uint32]*big.Int {
shards := []uint32{0, 1, 2, core.MetachainShardId}
rewardsPerShard := make(map[uint32]*big.Int)

for _, shardID := range shards {
rewardsPerShard[shardID] = big.NewInt(0) // Initialize reward entry
computeRewardsForShard(metaBlock, coordinator, validators, shardID, rewardsPerShard)
}

return rewardsPerShard
}

func computeRewardsForShard(metaBlock *apiCore.Block,
coordinator nodesCoordinator.NodesCoordinator,
validators map[string]string,
shardID uint32,
rewardsPerShard map[uint32]*big.Int,
) {
validatorsPerShard, _ := coordinator.GetAllEligibleValidatorsPublicKeysForShard(8, shardID)

for _, validator := range validatorsPerShard {
owner, exists := validators[hex.EncodeToString([]byte(validator))]
if !exists {
continue
}
accumulateShardRewards(metaBlock, shardID, owner, rewardsPerShard)
}
}

func accumulateShardRewards(metaBlock *apiCore.Block, shardID uint32, owner string, rewardsPerShard map[uint32]*big.Int) {
for _, mb := range metaBlock.MiniBlocks {
if mb.Type != block.RewardsBlock.String() {
continue
}

for _, tx := range mb.Transactions {
if tx.Receiver != owner {
continue
}

valueBig, _ := new(big.Int).SetString(tx.Value, 10)

rewardsPerShard[shardID].Add(rewardsPerShard[shardID], valueBig)
}
}
}

func readValidatorsAndOwners(filePath string) (map[string]string, error) {
file, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}

var nodesSetup struct {
InitialNodes []struct {
PubKey string `json:"pubkey"`
Address string `json:"address"`
} `json:"initialNodes"`
}

err = json.Unmarshal(file, &nodesSetup)
if err != nil {
return nil, err
}

validators := make(map[string]string)
for _, node := range nodesSetup.InitialNodes {
validators[node.PubKey] = node.Address
}

return validators, nil
}

func allValuesEqual(m map[uint32]*big.Int) bool {
if len(m) == 0 {
return true
}
expectedValue := m[0]
for _, v := range m {
if expectedValue.Cmp(v) != 0 {
return false
}
}
return true
}
34 changes: 33 additions & 1 deletion node/chainSimulator/chainSimulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"fmt"

"math/big"
"sync"
"time"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/core/sharding"
"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/endProcess"
"github.com/multiversx/mx-chain-core-go/data/transaction"
crypto "github.com/multiversx/mx-chain-crypto-go"
Expand Down Expand Up @@ -181,6 +183,8 @@ func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error {
s.initialWalletKeys = outputConfigs.InitialWallets
s.validatorsPrivateKeys = outputConfigs.ValidatorsPrivateKeys

s.addProofs()

log.Info("running the chain simulator with the following parameters",
"number of shards (including meta)", args.NumOfShards+1,
"round per epoch", outputConfigs.Configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch,
Expand All @@ -192,6 +196,27 @@ func (s *simulator) createChainHandlers(args ArgsBaseChainSimulator) error {
return nil
}

func (s *simulator) addProofs() {
proofs := make([]*block.HeaderProof, 0, len(s.nodes))

for shardID, nodeHandler := range s.nodes {
hash := nodeHandler.GetChainHandler().GetGenesisHeaderHash()
proofs = append(proofs, &block.HeaderProof{
HeaderShardId: shardID,
HeaderHash: hash,
})
}

metachainProofsPool := s.GetNodeHandler(core.MetachainShardId).GetDataComponents().Datapool().Proofs()
for _, proof := range proofs {
_ = metachainProofsPool.AddProof(proof)

if proof.HeaderShardId != core.MetachainShardId {
_ = s.GetNodeHandler(proof.HeaderShardId).GetDataComponents().Datapool().Proofs().AddProof(proof)
}
}
}

func computeStartTimeBaseOnInitialRound(args ArgsChainSimulator) int64 {
return args.GenesisTimestamp + int64(args.RoundDurationInMillis/1000)*args.InitialRound
}
Expand Down Expand Up @@ -313,7 +338,14 @@ func (s *simulator) ForceChangeOfEpoch() error {
epoch := s.nodes[core.MetachainShardId].GetProcessComponents().EpochStartTrigger().Epoch()
s.mutex.Unlock()

return s.GenerateBlocksUntilEpochIsReached(int32(epoch + 1))
err := s.GenerateBlocksUntilEpochIsReached(int32(epoch + 1))
if err != nil {
return err
}

s.incrementRoundOnAllValidators()

return s.allNodesCreateBlocks()
}

func (s *simulator) allNodesCreateBlocks() error {
Expand Down
2 changes: 1 addition & 1 deletion node/chainSimulator/process/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func createProofForHeader(pubKeyBitmap, signature, headerHash []byte, header dat
HeaderEpoch: header.GetEpoch(),
HeaderNonce: header.GetNonce(),
HeaderShardId: header.GetShardID(),
HeaderRound: header.GetNonce(),
HeaderRound: header.GetRound(),
IsStartOfEpoch: header.IsStartOfEpochBlock(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion process/block/metablock.go
Original file line number Diff line number Diff line change
Expand Up @@ -2263,7 +2263,7 @@ func (mp *metaProcessor) createShardInfo() ([]data.ShardDataHandler, error) {
}

isBlockAfterEquivalentMessagesFlag := !check.IfNil(headerInfo.hdr) &&
mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.EquivalentMessagesFlag, headerInfo.hdr.GetEpoch()) && headerInfo.hdr.GetNonce() > 1
mp.enableEpochsHandler.IsFlagEnabledInEpoch(common.EquivalentMessagesFlag, headerInfo.hdr.GetEpoch()) && headerInfo.hdr.GetNonce() >= 1
hasMissingShardHdrProof := isBlockAfterEquivalentMessagesFlag && !mp.proofsPool.HasProof(headerInfo.hdr.GetShardID(), []byte(hdrHash))
if hasMissingShardHdrProof {
return nil, fmt.Errorf("%w for shard header with hash %s", process.ErrMissingHeaderProof, hex.EncodeToString([]byte(hdrHash)))
Expand Down