Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for chain simulator #5697

Merged
merged 4 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
76 changes: 59 additions & 17 deletions node/chainSimulator/chainSimulator.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package chainSimulator

import (
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/data/endProcess"
"github.com/multiversx/mx-chain-go/config"
"github.com/multiversx/mx-chain-go/node/chainSimulator/components"
"github.com/multiversx/mx-chain-go/node/chainSimulator/configs"
"github.com/multiversx/mx-chain-go/node/chainSimulator/process"
"github.com/multiversx/mx-chain-go/node/chainSimulator/testdata"
logger "github.com/multiversx/mx-chain-logger-go"
)

var log = logger.GetOrCreate("chainSimulator")

type simulator struct {
chanStopNodeProcess chan endProcess.ArgEndProcess
syncedBroadcastNetwork components.SyncedBroadcastNetworkHandler
nodes []ChainHandler
handlers []ChainHandler
nodes map[uint32]process.NodeHandler
numOfShards uint32
}

Expand All @@ -23,17 +30,19 @@ func NewChainSimulator(
pathToInitialConfig string,
genesisTimestamp int64,
roundDurationInMillis uint64,
roundsPerEpoch core.OptionalUint64,
) (*simulator, error) {
syncedBroadcastNetwork := components.NewSyncedBroadcastNetwork()

instance := &simulator{
syncedBroadcastNetwork: syncedBroadcastNetwork,
nodes: make([]ChainHandler, 0),
nodes: make(map[uint32]process.NodeHandler),
handlers: make([]ChainHandler, 0, numOfShards+1),
numOfShards: numOfShards,
chanStopNodeProcess: make(chan endProcess.ArgEndProcess),
}

err := instance.createChainHandlers(tempDir, numOfShards, pathToInitialConfig, genesisTimestamp, roundDurationInMillis)
err := instance.createChainHandlers(tempDir, numOfShards, pathToInitialConfig, genesisTimestamp, roundDurationInMillis, roundsPerEpoch)
if err != nil {
return nil, err
}
Expand All @@ -47,6 +56,7 @@ func (s *simulator) createChainHandlers(
originalConfigPath string,
genesisTimestamp int64,
roundDurationInMillis uint64,
roundsPerEpoch core.OptionalUint64,
) error {
outputConfigs, err := configs.CreateChainSimulatorConfigs(configs.ArgsChainSimulatorConfigs{
NumOfShards: numOfShards,
Expand All @@ -61,23 +71,42 @@ func (s *simulator) createChainHandlers(
return err
}

if roundsPerEpoch.HasValue {
outputConfigs.Configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch = int64(roundsPerEpoch.Value)
}

for idx := range outputConfigs.ValidatorsPrivateKeys {
chainHandler, errCreate := s.createChainHandler(outputConfigs.Configs, idx, outputConfigs.GasScheduleFilename)
node, errCreate := s.createTestNode(outputConfigs.Configs, idx, outputConfigs.GasScheduleFilename)
if errCreate != nil {
return errCreate
}

s.nodes = append(s.nodes, chainHandler)
chainHandler, errCreate := process.NewBlocksCreator(node)
if errCreate != nil {
return errCreate
}

shardID := node.GetShardCoordinator().SelfId()
s.nodes[shardID] = node
s.handlers = append(s.handlers, chainHandler)
}

log.Info("running the chain simulator with the following parameters",
"number of shards (including meta)", numOfShards+1,
"round per epoch", outputConfigs.Configs.GeneralConfig.EpochStartConfig.RoundsPerEpoch,
"round duration", time.Millisecond*time.Duration(roundDurationInMillis),
"genesis timestamp", genesisTimestamp,
"original config path", originalConfigPath,
"temporary path", tempDir)

return nil
}

func (s *simulator) createChainHandler(
func (s *simulator) createTestNode(
configs *config.Configs,
skIndex int,
gasScheduleFilename string,
) (ChainHandler, error) {
) (process.NodeHandler, error) {
args := components.ArgsTestOnlyProcessingNode{
Config: *configs.GeneralConfig,
EpochConfig: *configs.EpochConfig,
Expand All @@ -95,12 +124,7 @@ func (s *simulator) createChainHandler(
SkIndex: skIndex,
}

testNode, err := components.NewTestOnlyProcessingNode(args)
if err != nil {
return nil, err
}

return process.NewBlocksCreator(testNode)
return components.NewTestOnlyProcessingNode(args)
}

// GenerateBlocks will generate the provided number of blocks
Expand All @@ -116,13 +140,13 @@ func (s *simulator) GenerateBlocks(numOfBlocks int) error {
}

func (s *simulator) incrementRoundOnAllValidators() {
for _, node := range s.nodes {
for _, node := range s.handlers {
node.IncrementRound()
}
}

func (s *simulator) allNodesCreateBlocks() error {
for _, node := range s.nodes {
for _, node := range s.handlers {
err := node.CreateNewBlock()
if err != nil {
return err
Expand All @@ -132,8 +156,26 @@ func (s *simulator) allNodesCreateBlocks() error {
return nil
}

// Stop will stop the simulator
func (s *simulator) Stop() {
// GetNodeHandler returns the node handler from the provided shardID
func (s *simulator) GetNodeHandler(shardID uint32) process.NodeHandler {
return s.nodes[shardID]
}

// Close will stop and close the simulator
func (s *simulator) Close() error {
var errorStrings []string
for _, n := range s.nodes {
err := n.Close()
if err != nil {
errorStrings = append(errorStrings, err.Error())
}
}

if len(errorStrings) == 0 {
return nil
}

return components.AggregateErrors(errorStrings, components.ErrClose)
}

// IsInterfaceNil returns true if there is no value under the interface
Expand Down
48 changes: 44 additions & 4 deletions node/chainSimulator/chainSimulator_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package chainSimulator

import (
"fmt"
"testing"
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-go/node/chainSimulator/testdata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -14,24 +18,60 @@ const (
func TestNewChainSimulator(t *testing.T) {
startTime := time.Now().Unix()
roundDurationInMillis := uint64(6000)
chainSimulator, err := NewChainSimulator(t.TempDir(), 3, defaultPathToInitialConfig, startTime, roundDurationInMillis)
chainSimulator, err := NewChainSimulator(t.TempDir(), 3, defaultPathToInitialConfig, startTime, roundDurationInMillis, core.OptionalUint64{})
require.Nil(t, err)
require.NotNil(t, chainSimulator)
defer chainSimulator.Stop()

time.Sleep(time.Second)

err = chainSimulator.Close()
assert.Nil(t, err)
}

func TestChainSimulator_GenerateBlocksShouldWork(t *testing.T) {
startTime := time.Now().Unix()
roundDurationInMillis := uint64(6000)
chainSimulator, err := NewChainSimulator(t.TempDir(), 3, defaultPathToInitialConfig, startTime, roundDurationInMillis)
chainSimulator, err := NewChainSimulator(t.TempDir(), 3, defaultPathToInitialConfig, startTime, roundDurationInMillis, core.OptionalUint64{})
require.Nil(t, err)
require.NotNil(t, chainSimulator)
defer chainSimulator.Stop()

time.Sleep(time.Second)

err = chainSimulator.GenerateBlocks(10)
require.Nil(t, err)

err = chainSimulator.Close()
assert.Nil(t, err)
}

func TestChainSimulator_GenerateBlocksAndEpochChangeShouldWork(t *testing.T) {
startTime := time.Now().Unix()
roundDurationInMillis := uint64(6000)
roundsPerEpoch := core.OptionalUint64{
HasValue: true,
Value: 20,
}
chainSimulator, err := NewChainSimulator(t.TempDir(), 3, defaultPathToInitialConfig, startTime, roundDurationInMillis, roundsPerEpoch)
require.Nil(t, err)
require.NotNil(t, chainSimulator)

facade, err := NewChainSimulatorFacade(chainSimulator)
require.Nil(t, err)

initialAccount, err := facade.GetExistingAccountFromBech32AddressString(testdata.GenesisAddressWithStake)
require.Nil(t, err)

time.Sleep(time.Second)

err = chainSimulator.GenerateBlocks(80)
require.Nil(t, err)

accountAfterRewards, err := facade.GetExistingAccountFromBech32AddressString(testdata.GenesisAddressWithStake)
require.Nil(t, err)

assert.True(t, accountAfterRewards.GetBalance().Cmp(initialAccount.GetBalance()) > 0,
fmt.Sprintf("initial balance %s, balance after rewards %s", initialAccount.GetBalance().String(), accountAfterRewards.GetBalance().String()))

err = chainSimulator.Close()
assert.Nil(t, err)
}
16 changes: 15 additions & 1 deletion node/chainSimulator/components/bootstrapComponents.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ArgsBootstrapComponentsHolder struct {
}

type bootstrapComponentsHolder struct {
closeHandler *closeHandler
epochStartBootstrapper factory.EpochStartBootstrapper
epochBootstrapParams factory.BootstrapParamsHolder
nodeType core.NodeType
Expand All @@ -38,7 +39,9 @@ type bootstrapComponentsHolder struct {

// CreateBootstrapComponentHolder will create a new instance of bootstrap components holder
func CreateBootstrapComponentHolder(args ArgsBootstrapComponentsHolder) (factory.BootstrapComponentsHolder, error) {
instance := &bootstrapComponentsHolder{}
instance := &bootstrapComponentsHolder{
closeHandler: NewCloseHandler(),
}

bootstrapComponentsFactoryArgs := bootstrapComp.BootstrapComponentsFactoryArgs{
Config: args.Config,
Expand Down Expand Up @@ -76,6 +79,8 @@ func CreateBootstrapComponentHolder(args ArgsBootstrapComponentsHolder) (factory
instance.headerIntegrityVerifier = managedBootstrapComponents.HeaderIntegrityVerifier()
instance.guardedAccountHandler = managedBootstrapComponents.GuardedAccountHandler()

instance.collectClosableComponents()

return instance, nil
}

Expand Down Expand Up @@ -119,6 +124,15 @@ func (b *bootstrapComponentsHolder) GuardedAccountHandler() process.GuardedAccou
return b.guardedAccountHandler
}

func (b *bootstrapComponentsHolder) collectClosableComponents() {
b.closeHandler.AddComponent(b.epochStartBootstrapper)
}

// Close will call the Close methods on all inner components
func (b *bootstrapComponentsHolder) Close() error {
return b.closeHandler.Close()
}

// IsInterfaceNil returns true if there is no value under the interface
func (b *bootstrapComponentsHolder) IsInterfaceNil() bool {
return b == nil
Expand Down
82 changes: 82 additions & 0 deletions node/chainSimulator/components/closeHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package components

import (
"errors"
"fmt"
"io"
"runtime/debug"
"strings"
"sync"

"github.com/multiversx/mx-chain-core-go/core/check"
)

// ErrClose signals that a close error occurred
var ErrClose = errors.New("error while closing inner components")

type errorlessCloser interface {
Close()
}

type allCloser interface {
CloseAll() error
}

type closeHandler struct {
mut sync.RWMutex
components []interface{}
}

// NewCloseHandler create a new closeHandler instance
func NewCloseHandler() *closeHandler {
return &closeHandler{
components: make([]interface{}, 0),
}
}

// AddComponent will try to add a component to the inner list if that component is not nil
func (handler *closeHandler) AddComponent(component interface{}) {
if check.IfNilReflect(component) {
log.Error("programming error in closeHandler.AddComponent: nil component", "stack", string(debug.Stack()))
return
}

handler.mut.Lock()
handler.components = append(handler.components, component)
handler.mut.Unlock()
}

// Close will try to close all components, wrapping errors, if necessary
func (handler *closeHandler) Close() error {
handler.mut.RLock()
defer handler.mut.RUnlock()

var errorStrings []string
for _, component := range handler.components {
var err error

switch t := component.(type) {
case errorlessCloser:
t.Close()
case io.Closer:
err = t.Close()
case allCloser:
err = t.CloseAll()
}

if err != nil {
errorStrings = append(errorStrings, fmt.Errorf("%w while closing the component of type %T", err, component).Error())
}
}

return AggregateErrors(errorStrings, ErrClose)
}

// AggregateErrors can aggregate all provided error strings into a single error variable
func AggregateErrors(errorStrings []string, baseError error) error {
if len(errorStrings) == 0 {
return nil
}

return fmt.Errorf("%w %s", baseError, strings.Join(errorStrings, ", "))
}