diff --git a/cmd/node/CLI.md b/cmd/node/CLI.md index cd5b4b6e2ac..86809e614fe 100644 --- a/cmd/node/CLI.md +++ b/cmd/node/CLI.md @@ -27,6 +27,7 @@ GLOBAL OPTIONS: --config-external [path] The [path] for the external configuration file. This TOML file contains external configurations such as ElasticSearch's URL and login information (default: "./config/external.toml") --p2p-config [path] The [path] for the p2p configuration file. This TOML file contains peer-to-peer configurations such as port, target peer count or KadDHT settings (default: "./config/p2p.toml") --full-archive-p2p-config [path] The [path] for the p2p configuration file for the full archive network. This TOML file contains peer-to-peer configurations such as port, target peer count or KadDHT settings (default: "./config/fullArchiveP2P.toml") + --light-client-p2p-config [path] The [path] for the p2p configuration file for the light client network. This TOML file contains peer-to-peer configurations such as port, target peer count or KadDHT settings (default: "./config/lightClientP2P.toml") --epoch-config [path] The [path] for the epoch configuration file. This TOML file contains activation epochs configurations (default: "./config/enableEpochs.toml") --round-config [path] The [path] for the round configuration file. This TOML file contains activation round configurations (default: "./config/enableRounds.toml") --gas-costs-config [path] The [path] for the gas costs configuration directory. (default: "./config/gasSchedules") @@ -60,6 +61,8 @@ GLOBAL OPTIONS: --import-db-save-epoch-root-hash This flag, if set, will export the trie snapshots at every new epoch --redundancy-level value This flag specifies the level of redundancy used by the current instance for the node (-1 = disabled, 0 = main instance (default), 1 = first backup, 2 = second backup, etc.) (default: 0) --full-archive Boolean option for settings an observer as full archive, which will sync the entire database of its shard + --light-client Boolean option for setting an observer as light client + --light-client-supplier Boolean option for setting an observer as light client supplier --mem-ballast value Flag that specifies the number of MegaBytes to be used as a memory ballast for Garbage Collector optimization. If set to 0 (or not set at all), the feature will be disabled. This flag should be used only for well-monitored nodes and by advanced users, as a too high memory ballast could lead to Out Of Memory panics. The memory ballast should not be higher than 20-25% of the machine's available RAM (default: 0) --memory-usage-to-create-profiles value Integer value to be used to set the memory usage thresholds (in bytes) (default: 2415919104) --force-start-from-network Flag that will force the start from network bootstrap process diff --git a/cmd/node/config/lightClientP2P.toml b/cmd/node/config/lightClientP2P.toml new file mode 100644 index 00000000000..3ed38df0c55 --- /dev/null +++ b/cmd/node/config/lightClientP2P.toml @@ -0,0 +1,89 @@ +# LightClientP2P config file + +# NodeConfig holds the P2P settings +[Node] + # Port is the port that will be opened by the node on all interfaces so other peers can connect to it + # If the port = 0, the node will search for a free port on the machine and use it + Port = "37373-38383" + + # ThresholdMinConnectedPeers represents the minimum number of connections a node should have before it can start + # the sync and consensus mechanisms + ThresholdMinConnectedPeers = 3 + + # MinNumPeersToWaitForOnBootstrap is the minimum number of peers to wait on bootstrap or the node will wait the default + # time which is now set to ~20 seconds (the const defined in the common package named TimeToWaitForP2PBootstrap) + MinNumPeersToWaitForOnBootstrap = 10 + + # available transports. All defined addresses contains a single '%d' markup that is mandatory and will + # be replaced at runtime with the actual port value + [Node.Transports] + QUICAddress = "" # optional QUIC address. If this transport should be activated, should be in this format: /ip4/0.0.0.0/udp/%d/quic-v1 + WebSocketAddress = "" # optional WebSocket address. If this transport should be activated, should be in this format: /ip4/0.0.0.0/tcp/%d/ws + WebTransportAddress = "" # optional WebTransport address. If this transport should be activated, should be in this format: /ip4/0.0.0.0/udp/%d/quic-v1/webtransport + [Node.Transports.TCP] + ListenAddress = "/ip4/0.0.0.0/tcp/%d" # TCP listen address + PreventPortReuse = false + + [Node.ResourceLimiter] + Type = "default autoscale" #available options "default autoscale", "infinite", "default with manual scale". + ManualSystemMemoryInMB = 0 # not taken into account if the type is not "default with manual scale" + ManualMaximumFD = 0 # not taken into account if the type is not "default with manual scale" + +# P2P peer discovery section + +# The following sections correspond to the way new peers will be discovered +# If all config types are disabled then the peer will run in single mode (will not try to find other peers) +# If more than one peer discovery mechanism is enabled, the application will output an error and will not start + +[KadDhtPeerDiscovery] + # Enabled: true/false to enable/disable this discovery mechanism + Enabled = true + + # Type represents the kad-dht glue code implementation. + # "legacy" will define the first implementation. + # "optimized" represents the new variant able to connect to multiple seeders at once. This implementation also has + # a built-in timer that will try to automatically reconnect to the seeders (in case the seeders recover after a + # premature shutdown) + Type = "optimized" + + # RefreshIntervalInSec represents the time in seconds between querying for new peers + RefreshIntervalInSec = 10 + + # ProtocolIDs represents the protocols that this node will advertise to other peers + # To connect to other nodes, those nodes should have at least one common protocol string + ProtocolIDs = [ + "/mvx/light/1.0.0", + ] + + # InitialPeerList represents the list of strings of some known nodes that will bootstrap this node + # The address will be in a self-describing addressing format. + # More can be found here: https://github.com/libp2p/specs/blob/master/3-requirements.md#34-transport-agnostic + # Example: + # /ip6/fe80::8823:6dff:fee7:f172/tcp/4001/p2p/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu + # /ip4/162.246.145.218/udp/4001/utp/ipfs/QmYJyUMAcXEw1b5bFfbBbzYu5wyyjLMRHXGUkCXpag74Fu + # + # If the initial peers list is left empty, the node will not try to connect to other peers during initial bootstrap + # phase but will accept connections and will do the network discovery if another peer connects to it + InitialPeerList = ["/ip4/127.0.0.1/tcp/9999/p2p/16Uiu2HAkw5SNNtSvH1zJiQ6Gc3WoGNSxiyNueRKe6fuAuh57G3Bk"] + + # kademlia's routing table bucket size + BucketSize = 100 + + # RoutingTableRefreshIntervalInSec defines how many seconds should pass between 2 kad routing table auto refresh calls + RoutingTableRefreshIntervalInSec = 300 + +[Sharding] + # The targeted number of peer connections + TargetPeerCount = 41 + MaxIntraShardValidators = 7 + MaxCrossShardValidators = 15 + MaxIntraShardObservers = 7 + MaxCrossShardObservers = 3 + MaxSeeders = 2 + + # available options: + # `ListsSharder` will split the peers based on the shard membership (intra, cross or unknown) + # `OneListSharder` will do just the connection triming (upto TargetPeerCount value) not taking into account + # the shard membership of the connected peers + # `NilListSharder` will disable conection trimming (sharder is off) + Type = "ListsSharder" diff --git a/cmd/node/config/prefs.toml b/cmd/node/config/prefs.toml index 8f3a2343a79..0bd8869a2e8 100644 --- a/cmd/node/config/prefs.toml +++ b/cmd/node/config/prefs.toml @@ -20,6 +20,14 @@ # It is highly recommended to enable this flag on an observer (not on a validator node) FullArchive = false + # LightClient, if enabled, will make the node able to connect on the light client network. + # It is highly recommended to enable this flag on an observer (not on a validator node) + LightClient = false + + # LightClientSupplier, if enabled, will make the node able to connect on the light client network as a supplier. + # It is highly recommended to enable this flag on an observer (not on a validator node) + LightClientSupplier = false + # PreferredConnections holds an array containing valid ips or peer ids from nodes to connect with (in top of other connections) # Example: # PreferredConnections = [ diff --git a/cmd/node/flags.go b/cmd/node/flags.go index 72c86c04f96..774ca875475 100644 --- a/cmd/node/flags.go +++ b/cmd/node/flags.go @@ -1,17 +1,19 @@ package main import ( + "errors" "fmt" "math" "os" "runtime" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/urfave/cli" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/operationmodes" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/facade" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/urfave/cli" ) var ( @@ -98,6 +100,12 @@ var ( "configurations such as port, target peer count or KadDHT settings", Value: "./config/fullArchiveP2P.toml", } + lightClientP2PConfigurationFile = cli.StringFlag{ + Name: "light-client-p2p-config", + Usage: "The `" + filePathPlaceholder + "` for the p2p configuration file for the light client network. This TOML file contains peer-to-peer " + + "configurations such as port, target peer count or KadDHT settings", + Value: "./config/lightClientP2P.toml", + } // epochConfigurationFile defines a flag for the path to the toml file containing the epoch configuration epochConfigurationFile = cli.StringFlag{ Name: "epoch-config", @@ -342,6 +350,16 @@ var ( Name: "full-archive", Usage: "Boolean option for settings an observer as full archive, which will sync the entire database of its shard", } + // lightClient defines a flag that, if set, will make the node act like a light client + lightClient = cli.BoolFlag{ + Name: "light-client", + Usage: "Boolean option for setting an observer as light client", + } + // lightClientSupplier defines a flag that, if set, will make the node act like a light client supplier + lightClientSupplier = cli.BoolFlag{ + Name: "light-client-supplier", + Usage: "Boolean option for setting an observer as light client supplier", + } // memBallast defines a flag that specifies the number of MegaBytes to be used as a memory ballast for Garbage Collector optimization // if set to 0, the memory ballast won't be used memBallast = cli.Uint64Flag{ @@ -424,6 +442,7 @@ func getFlags() []cli.Flag { externalConfigFile, p2pConfigurationFile, fullArchiveP2PConfigurationFile, + lightClientP2PConfigurationFile, epochConfigurationFile, roundConfigurationFile, gasScheduleConfigurationDirectory, @@ -457,6 +476,8 @@ func getFlags() []cli.Flag { importDbSaveEpochRootHash, redundancyLevel, fullArchive, + lightClient, + lightClientSupplier, memBallast, memoryUsageToCreateProfiles, forceStartFromNetwork, @@ -535,6 +556,12 @@ func applyFlags(ctx *cli.Context, cfgs *config.Configs, flagsConfig *config.Cont if ctx.IsSet(fullArchive.Name) { cfgs.PreferencesConfig.Preferences.FullArchive = ctx.GlobalBool(fullArchive.Name) } + if ctx.IsSet(lightClient.Name) { + cfgs.PreferencesConfig.Preferences.LightClient = ctx.GlobalBool(lightClient.Name) + } + if ctx.IsSet(lightClientSupplier.Name) { + cfgs.PreferencesConfig.Preferences.LightClientSupplier = ctx.GlobalBool(lightClientSupplier.Name) + } if ctx.IsSet(memoryUsageToCreateProfiles.Name) { cfgs.GeneralConfig.Health.MemoryUsageToCreateProfiles = int(ctx.GlobalUint64(memoryUsageToCreateProfiles.Name)) log.Info("setting a new value for the memoryUsageToCreateProfiles option", @@ -647,6 +674,12 @@ func applyCompatibleConfigs(log logger.Logger, configs *config.Configs) error { processSnapshotLessObserverMode(log, configs) } + isBothLightClientConsumerAndSupplier := configs.PreferencesConfig.Preferences.LightClientSupplier && + configs.PreferencesConfig.Preferences.LightClient + if isBothLightClientConsumerAndSupplier { + return errors.New("cannot have node running in both light client consumer and supplier mode") + } + return nil } diff --git a/cmd/node/main.go b/cmd/node/main.go index c7cc3c1085c..6099ee44c1d 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -9,14 +9,15 @@ import ( "github.com/klauspost/cpuid/v2" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-logger-go/file" + "github.com/urfave/cli" + "github.com/multiversx/mx-chain-go/cmd/node/factory" "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/config/overridableConfig" "github.com/multiversx/mx-chain-go/node" - logger "github.com/multiversx/mx-chain-logger-go" - "github.com/multiversx/mx-chain-logger-go/file" - "github.com/urfave/cli" // test point 1 for custom profiler ) @@ -224,6 +225,13 @@ func readConfigs(ctx *cli.Context, log logger.Logger) (*config.Configs, error) { } log.Debug("config", "file", configurationPaths.FullArchiveP2p) + configurationPaths.LightClientP2p = ctx.GlobalString(lightClientP2PConfigurationFile.Name) + lightClientP2PConfig, err := common.LoadP2PConfig(configurationPaths.LightClientP2p) + if err != nil { + return nil, err + } + log.Debug("config", "file", configurationPaths.LightClientP2p) + configurationPaths.Epoch = ctx.GlobalString(epochConfigurationFile.Name) epochConfig, err := common.LoadEpochConfig(configurationPaths.Epoch) if err != nil { @@ -264,6 +272,7 @@ func readConfigs(ctx *cli.Context, log logger.Logger) (*config.Configs, error) { ExternalConfig: externalConfig, MainP2pConfig: mainP2PConfig, FullArchiveP2pConfig: fullArchiveP2PConfig, + LightClientP2pConfig: lightClientP2PConfig, ConfigurationPathsHolder: configurationPaths, EpochConfig: epochConfig, RoundConfig: roundConfig, diff --git a/common/constants.go b/common/constants.go index 984dec87b07..81876301fac 100644 --- a/common/constants.go +++ b/common/constants.go @@ -16,6 +16,12 @@ const NormalOperation NodeOperation = "normal operation" // FullArchiveMode defines the node operation as a full archive mode const FullArchiveMode NodeOperation = "full archive mode" +// LightClientMode defines the node operation as a light client +const LightClientMode NodeOperation = "light client mode" + +// LightClientSupplierMode defines the node operation light client supplier +const LightClientSupplierMode NodeOperation = "light client supplier mode" + // PeerType represents the type of peer type PeerType string diff --git a/common/generics.go b/common/generics.go new file mode 100644 index 00000000000..8406754ade1 --- /dev/null +++ b/common/generics.go @@ -0,0 +1,13 @@ +package common + +// Contains returns true if the specified value is contained in a slice. +func Contains[T comparable](s []T, e ...T) bool { + for _, v := range s { + for _, v2 := range e { + if v == v2 { + return true + } + } + } + return false +} diff --git a/config/config.go b/config/config.go index 412ab59f776..cf0ba12fc80 100644 --- a/config/config.go +++ b/config/config.go @@ -588,6 +588,7 @@ type Configs struct { ExternalConfig *ExternalConfig MainP2pConfig *p2pConfig.P2PConfig FullArchiveP2pConfig *p2pConfig.P2PConfig + LightClientP2pConfig *p2pConfig.P2PConfig FlagsConfig *ContextFlagsConfig ImportDbConfig *ImportDbConfig ConfigurationPathsHolder *ConfigurationPathsHolder @@ -606,6 +607,7 @@ type ConfigurationPathsHolder struct { External string MainP2p string FullArchiveP2p string + LightClientP2p string GasScheduleDirectoryName string Nodes string Genesis string diff --git a/config/prefsConfig.go b/config/prefsConfig.go index 2659e592364..6578b4849e6 100644 --- a/config/prefsConfig.go +++ b/config/prefsConfig.go @@ -17,6 +17,8 @@ type PreferencesConfig struct { ConnectionWatcherType string OverridableConfigTomlValues []OverridableConfig FullArchive bool + LightClient bool + LightClientSupplier bool } // OverridableConfig holds the path and the new value to be updated in the configuration diff --git a/errors/errors.go b/errors/errors.go index b1edc990afc..d22435dbcf7 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -590,8 +590,14 @@ var ErrNilBLSPublicKey = errors.New("bls public key is nil") // ErrEmptyAddress defines the error when trying to work with an empty address var ErrEmptyAddress = errors.New("empty Address") -// ErrInvalidNodeOperationMode signals that an invalid node operation mode has been provided -var ErrInvalidNodeOperationMode = errors.New("invalid node operation mode") +// ErrInvalidOperationMode signals that the operation mode is invalid +var ErrInvalidOperationMode = errors.New("invalid operation mode") + +// ErrInvalidMainNodeOperationMode signals that an invalid node operation mode has been provided +var ErrInvalidMainNodeOperationMode = errors.New("node operation modes does not contain Normal or FullArchive") + +// ErrInvalidNodeOperationModeCombo signals that an invalid node operation mode combination has been provided +var ErrInvalidNodeOperationModeCombo = errors.New("invalid node operation mode combo") // ErrNilSentSignatureTracker defines the error for setting a nil SentSignatureTracker var ErrNilSentSignatureTracker = errors.New("nil sent signature tracker") diff --git a/factory/network/networkComponents.go b/factory/network/networkComponents.go index afe5757ecd7..7f0c97bca50 100644 --- a/factory/network/networkComponents.go +++ b/factory/network/networkComponents.go @@ -8,6 +8,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" "github.com/multiversx/mx-chain-core-go/marshal" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/consensus" @@ -25,13 +27,13 @@ import ( "github.com/multiversx/mx-chain-go/storage/cache" storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" - logger "github.com/multiversx/mx-chain-logger-go" ) // NetworkComponentsFactoryArgs holds the arguments to create a network component handler instance type NetworkComponentsFactoryArgs struct { MainP2pConfig p2pConfig.P2PConfig FullArchiveP2pConfig p2pConfig.P2PConfig + LightClientP2pConfig p2pConfig.P2PConfig MainConfig config.Config RatingsConfig config.RatingsConfig StatusHandler core.AppStatusHandler @@ -39,7 +41,7 @@ type NetworkComponentsFactoryArgs struct { Syncer p2p.SyncTimer PreferredPeersSlices []string BootstrapWaitTime time.Duration - NodeOperationMode common.NodeOperation + NodeOperationModes []common.NodeOperation ConnectionWatcherType string CryptoComponents factory.CryptoComponentsHolder } @@ -47,6 +49,7 @@ type NetworkComponentsFactoryArgs struct { type networkComponentsFactory struct { mainP2PConfig p2pConfig.P2PConfig fullArchiveP2PConfig p2pConfig.P2PConfig + lightClientP2PConfig p2pConfig.P2PConfig mainConfig config.Config ratingsConfig config.RatingsConfig statusHandler core.AppStatusHandler @@ -54,7 +57,7 @@ type networkComponentsFactory struct { syncer p2p.SyncTimer preferredPeersSlices []string bootstrapWaitTime time.Duration - nodeOperationMode common.NodeOperation + nodeOperationModes []common.NodeOperation connectionWatcherType string cryptoComponents factory.CryptoComponentsHolder } @@ -68,6 +71,7 @@ type networkComponentsHolder struct { type networkComponents struct { mainNetworkHolder networkComponentsHolder fullArchiveNetworkHolder networkComponentsHolder + lightClientNetworkHolder networkComponentsHolder peersRatingHandler p2p.PeersRatingHandler peersRatingMonitor p2p.PeersRatingMonitor inputAntifloodHandler factory.P2PAntifloodHandler @@ -99,13 +103,16 @@ func NewNetworkComponentsFactory( if check.IfNil(args.CryptoComponents) { return nil, errors.ErrNilCryptoComponentsHolder } - if args.NodeOperationMode != common.NormalOperation && args.NodeOperationMode != common.FullArchiveMode { - return nil, errors.ErrInvalidNodeOperationMode + + err := checkNodeOperationModes(args.NodeOperationModes) + if err != nil { + return nil, err } return &networkComponentsFactory{ mainP2PConfig: args.MainP2pConfig, fullArchiveP2PConfig: args.FullArchiveP2pConfig, + lightClientP2PConfig: args.LightClientP2pConfig, ratingsConfig: args.RatingsConfig, marshalizer: args.Marshalizer, mainConfig: args.MainConfig, @@ -113,7 +120,7 @@ func NewNetworkComponentsFactory( syncer: args.Syncer, bootstrapWaitTime: args.BootstrapWaitTime, preferredPeersSlices: args.PreferredPeersSlices, - nodeOperationMode: args.NodeOperationMode, + nodeOperationModes: args.NodeOperationModes, connectionWatcherType: args.ConnectionWatcherType, cryptoComponents: args.CryptoComponents, }, nil @@ -136,6 +143,11 @@ func (ncf *networkComponentsFactory) Create() (*networkComponents, error) { return nil, fmt.Errorf("%w for the full archive network holder", err) } + lightClientNetworkComp, err := ncf.createLightClientNetworkHolder(peersRatingHandler) + if err != nil { + return nil, fmt.Errorf("%w for the light client network holder", err) + } + ctx, cancelFunc := context.WithCancel(context.Background()) defer func() { if err != nil { @@ -160,9 +172,15 @@ func (ncf *networkComponentsFactory) Create() (*networkComponents, error) { return nil, err } + err = lightClientNetworkComp.netMessenger.Bootstrap() + if err != nil { + return nil, err + } + return &networkComponents{ mainNetworkHolder: mainNetworkComp, fullArchiveNetworkHolder: fullArchiveNetworkComp, + lightClientNetworkHolder: lightClientNetworkComp, peersRatingHandler: peersRatingHandler, peersRatingMonitor: peersRatingMonitor, inputAntifloodHandler: inputAntifloodHandler, @@ -279,7 +297,7 @@ func (ncf *networkComponentsFactory) createMainNetworkHolder(peersRatingHandler } func (ncf *networkComponentsFactory) createFullArchiveNetworkHolder(peersRatingHandler p2p.PeersRatingHandler) (networkComponentsHolder, error) { - if ncf.nodeOperationMode != common.FullArchiveMode { + if !common.Contains(ncf.nodeOperationModes, common.FullArchiveMode) { return networkComponentsHolder{ netMessenger: p2pDisabled.NewNetworkMessenger(), preferredPeersHolder: disabled.NewPreferredPeersHolder(), @@ -291,6 +309,20 @@ func (ncf *networkComponentsFactory) createFullArchiveNetworkHolder(peersRatingH return ncf.createNetworkHolder(ncf.fullArchiveP2PConfig, loggerInstance, peersRatingHandler, p2p.FullArchiveNetwork) } +func (ncf *networkComponentsFactory) createLightClientNetworkHolder(peersRatingHandler p2p.PeersRatingHandler) (networkComponentsHolder, error) { + if !common.Contains(ncf.nodeOperationModes, common.LightClientMode) && + !common.Contains(ncf.nodeOperationModes, common.LightClientSupplierMode) { + return networkComponentsHolder{ + netMessenger: p2pDisabled.NewNetworkMessenger(), + preferredPeersHolder: disabled.NewPreferredPeersHolder(), + }, nil + } + + loggerInstance := logger.GetOrCreate("light-client/p2p") + + return ncf.createNetworkHolder(ncf.lightClientP2PConfig, loggerInstance, peersRatingHandler, p2p.LightClientNetwork) +} + func (ncf *networkComponentsFactory) createPeersRatingComponents() (p2p.PeersRatingHandler, p2p.PeersRatingMonitor, error) { peersRatingCfg := ncf.mainConfig.PeersRatingConfig topRatedCache, err := cache.NewLRUCache(peersRatingCfg.TopRatedCacheCapacity) @@ -351,5 +383,48 @@ func (nc *networkComponents) Close() error { log.LogIfError(fullArchiveNetMessenger.Close()) } + lightClientMessenger := nc.lightClientNetworkHolder.netMessenger + if !check.IfNil(lightClientMessenger) { + log.Debug("calling close on the light client network messenger instance...") + log.LogIfError(lightClientMessenger.Close()) + } + + return nil +} + +func checkNodeOperationModes(nodeOperationModes []common.NodeOperation) error { + // check if there are more than 2 simultaneous operating modes + if len(nodeOperationModes) > 2 { + return fmt.Errorf("cannot have more than 2 node operation modes, got %d modes instead", + len(nodeOperationModes)) + } + + // if the node modes doesn't contain any of the valid ones, then the configuration is invalid + if !common.Contains(nodeOperationModes, []common.NodeOperation{ + common.NormalOperation, + common.FullArchiveMode, + common.LightClientMode, + common.LightClientSupplierMode}...) { + return errors.ErrInvalidOperationMode + } + + // the node must contain at least one of the following: common.NormalOperation or common.FullArchive + if !common.Contains(nodeOperationModes, common.NormalOperation) && + !common.Contains(nodeOperationModes, common.FullArchiveMode) { + return errors.ErrInvalidMainNodeOperationMode + } + + // the node cannot be in both normal and full archive modes + if common.Contains(nodeOperationModes, common.NormalOperation) && + common.Contains(nodeOperationModes, common.FullArchiveMode) { + return errors.ErrInvalidNodeOperationModeCombo + } + + // the node cannot be in both light client & light client supplier mode simultaneously + if common.Contains(nodeOperationModes, common.LightClientMode) && + common.Contains(nodeOperationModes, common.LightClientSupplierMode) { + return errors.ErrInvalidNodeOperationModeCombo + } + return nil } diff --git a/factory/network/networkComponentsHandler.go b/factory/network/networkComponentsHandler.go index eda76bb8f28..44b9793b2f8 100644 --- a/factory/network/networkComponentsHandler.go +++ b/factory/network/networkComponentsHandler.go @@ -5,6 +5,7 @@ import ( "sync" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-go/errors" "github.com/multiversx/mx-chain-go/factory" "github.com/multiversx/mx-chain-go/p2p" @@ -18,6 +19,7 @@ var _ factory.NetworkComponentsHandler = (*managedNetworkComponents)(nil) const ( errorOnMainNetworkString = "on main network" errorOnFullArchiveNetworkString = "on full archive network" + errorOnLightClientNetworkString = "on light client network" ) // managedNetworkComponents creates the data components handler that can create, close and access the data components @@ -93,6 +95,10 @@ func (mnc *managedNetworkComponents) CheckSubcomponents() error { return fmt.Errorf("%w %s", errors.ErrNilMessenger, errorOnFullArchiveNetworkString) } + if check.IfNil(mnc.lightClientNetworkHolder.netMessenger) { + return fmt.Errorf("%w %s", errors.ErrNilMessenger, errorOnLightClientNetworkString) + } + if check.IfNil(mnc.inputAntifloodHandler) { return errors.ErrNilInputAntiFloodHandler } diff --git a/factory/network/networkComponents_test.go b/factory/network/networkComponents_test.go index 221651b22fb..0ef09809ab1 100644 --- a/factory/network/networkComponents_test.go +++ b/factory/network/networkComponents_test.go @@ -2,12 +2,15 @@ package network_test import ( "errors" + "fmt" "testing" + "github.com/stretchr/testify/require" + + "github.com/multiversx/mx-chain-go/common" errorsMx "github.com/multiversx/mx-chain-go/errors" networkComp "github.com/multiversx/mx-chain-go/factory/network" componentsMock "github.com/multiversx/mx-chain-go/testscommon/components" - "github.com/stretchr/testify/require" ) func TestNewNetworkComponentsFactory(t *testing.T) { @@ -49,23 +52,88 @@ func TestNewNetworkComponentsFactory(t *testing.T) { require.Nil(t, ncf) require.Equal(t, errorsMx.ErrNilCryptoComponentsHolder, err) }) - t.Run("invalid node operation mode should error", func(t *testing.T) { + t.Run("invalid node operation modes should error", func(t *testing.T) { t.Parallel() - args := componentsMock.GetNetworkFactoryArgs() - args.NodeOperationMode = "invalid" + t.Run("invalid mode", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + args.NodeOperationModes = []common.NodeOperation{"invalid"} + + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Equal(t, errorsMx.ErrInvalidOperationMode, err) + require.Nil(t, ncf) + }) + + t.Run("invalid length", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + args.NodeOperationModes = []common.NodeOperation{common.FullArchiveMode, common.LightClientMode, + common.LightClientSupplierMode} + + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Equal(t, fmt.Errorf("cannot have more than 2 node operation modes, got %d modes instead", + len(args.NodeOperationModes)), err) + require.Nil(t, ncf) + }) + + t.Run("invalid main mode", func(t *testing.T) { + t.Parallel() + args := componentsMock.GetNetworkFactoryArgs() + args.NodeOperationModes = []common.NodeOperation{common.LightClientSupplierMode, common.LightClientMode} + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Equal(t, errorsMx.ErrInvalidMainNodeOperationMode, err) + require.Nil(t, ncf) + }) + + t.Run("invalid combo", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + args.NodeOperationModes = []common.NodeOperation{common.NormalOperation, common.FullArchiveMode} + + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Equal(t, errorsMx.ErrInvalidNodeOperationModeCombo, err) + require.Nil(t, ncf) + }) + + t.Run("valid combo", func(t *testing.T) { + t.Parallel() + + args := componentsMock.GetNetworkFactoryArgs() + args.NodeOperationModes = []common.NodeOperation{common.NormalOperation} + ncf, err := networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + + args.NodeOperationModes = []common.NodeOperation{common.FullArchiveMode} + ncf, err = networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + + args.NodeOperationModes = []common.NodeOperation{common.NormalOperation, common.LightClientMode} + ncf, err = networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + + args.NodeOperationModes = []common.NodeOperation{common.NormalOperation, common.LightClientSupplierMode} + ncf, err = networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + + args.NodeOperationModes = []common.NodeOperation{common.FullArchiveMode, common.LightClientMode} + ncf, err = networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + + args.NodeOperationModes = []common.NodeOperation{common.FullArchiveMode, common.LightClientSupplierMode} + ncf, err = networkComp.NewNetworkComponentsFactory(args) + require.Nil(t, err) + require.NotNil(t, ncf) + }) - ncf, err := networkComp.NewNetworkComponentsFactory(args) - require.Equal(t, errorsMx.ErrInvalidNodeOperationMode, err) - require.Nil(t, ncf) - }) - t.Run("should work", func(t *testing.T) { - t.Parallel() - - args := componentsMock.GetNetworkFactoryArgs() - ncf, err := networkComp.NewNetworkComponentsFactory(args) - require.NoError(t, err) - require.NotNil(t, ncf) }) } diff --git a/integrationTests/factory/componentsHelper.go b/integrationTests/factory/componentsHelper.go index 6ad6c5910bf..952cbd78a48 100644 --- a/integrationTests/factory/componentsHelper.go +++ b/integrationTests/factory/componentsHelper.go @@ -7,10 +7,11 @@ import ( "runtime/pprof" "testing" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" "github.com/multiversx/mx-chain-go/p2p" - logger "github.com/multiversx/mx-chain-logger-go" ) var log = logger.GetOrCreate("integrationtests") @@ -36,6 +37,7 @@ func CreateDefaultConfig(tb testing.TB) *config.Configs { prefsConfig, _ := common.LoadPreferencesConfig(configPathsHolder.Preferences) mainP2PConfig, _ := common.LoadP2PConfig(configPathsHolder.MainP2p) fullArchiveP2PConfig, _ := common.LoadP2PConfig(configPathsHolder.FullArchiveP2p) + lightClientP2PConfig, _ := common.LoadP2PConfig(configPathsHolder.LightClientP2p) externalConfig, _ := common.LoadExternalConfig(configPathsHolder.External) systemSCConfig, _ := common.LoadSystemSmartContractsConfig(configPathsHolder.SystemSC) epochConfig, _ := common.LoadEpochConfig(configPathsHolder.Epoch) @@ -53,6 +55,7 @@ func CreateDefaultConfig(tb testing.TB) *config.Configs { configs.PreferencesConfig = prefsConfig configs.MainP2pConfig = mainP2PConfig configs.FullArchiveP2pConfig = fullArchiveP2PConfig + configs.LightClientP2pConfig = lightClientP2PConfig configs.ExternalConfig = externalConfig configs.EpochConfig = epochConfig configs.RoundConfig = roundConfig @@ -86,6 +89,7 @@ func createConfigurationsPathsHolder() *config.ConfigurationPathsHolder { External: concatPath(ExternalPath), MainP2p: concatPath(MainP2pPath), FullArchiveP2p: concatPath(FullArchiveP2pPath), + LightClientP2p: concatPath(LightClientP2pPath), Epoch: concatPath(EpochPath), SystemSC: concatPath(SystemSCConfigPath), GasScheduleDirectoryName: concatPath(GasSchedule), diff --git a/integrationTests/factory/constants.go b/integrationTests/factory/constants.go index 9fa9133b135..6281aa17d50 100644 --- a/integrationTests/factory/constants.go +++ b/integrationTests/factory/constants.go @@ -10,6 +10,7 @@ const ( ExternalPath = "external.toml" MainP2pPath = "p2p.toml" FullArchiveP2pPath = "fullArchiveP2P.toml" + LightClientP2pPath = "lightClientP2P.toml" EpochPath = "enableEpochs.toml" SystemSCConfigPath = "systemSmartContractsConfig.toml" GasSchedule = "gasSchedules" diff --git a/integrationTests/realcomponents/processorRunner.go b/integrationTests/realcomponents/processorRunner.go index f788de20f84..020dca6de6f 100644 --- a/integrationTests/realcomponents/processorRunner.go +++ b/integrationTests/realcomponents/processorRunner.go @@ -13,6 +13,8 @@ import ( "github.com/multiversx/mx-chain-core-go/data/endProcess" "github.com/multiversx/mx-chain-core-go/data/esdt" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/common/forking" "github.com/multiversx/mx-chain-go/common/ordering" @@ -40,7 +42,6 @@ import ( storageFactory "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/storageunit" "github.com/multiversx/mx-chain-go/update/trigger" - "github.com/stretchr/testify/require" ) // ProcessorRunner is a test emulation to the nodeRunner component @@ -175,7 +176,7 @@ func (pr *ProcessorRunner) createNetworkComponents(tb testing.TB) { Syncer: pr.CoreComponents.SyncTimer(), PreferredPeersSlices: make([]string, 0), BootstrapWaitTime: 1, - NodeOperationMode: common.NormalOperation, + NodeOperationModes: []common.NodeOperation{common.NormalOperation}, ConnectionWatcherType: "", CryptoComponents: pr.CryptoComponents, } diff --git a/node/nodeRunner.go b/node/nodeRunner.go index 54ffe84b4e3..93aef8755c9 100644 --- a/node/nodeRunner.go +++ b/node/nodeRunner.go @@ -20,6 +20,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core/throttler" "github.com/multiversx/mx-chain-core-go/data/endProcess" outportCore "github.com/multiversx/mx-chain-core-go/data/outport" + logger "github.com/multiversx/mx-chain-logger-go" + "github.com/multiversx/mx-chain-go/api/gin" "github.com/multiversx/mx-chain-go/api/shared" "github.com/multiversx/mx-chain-go/common" @@ -61,7 +63,6 @@ import ( "github.com/multiversx/mx-chain-go/storage/storageunit" trieStatistics "github.com/multiversx/mx-chain-go/trie/statistics" "github.com/multiversx/mx-chain-go/update/trigger" - logger "github.com/multiversx/mx-chain-logger-go" ) type nextOperationForNode int @@ -1417,6 +1418,7 @@ func (nr *nodeRunner) CreateManagedNetworkComponents( networkComponentsFactoryArgs := networkComp.NetworkComponentsFactoryArgs{ MainP2pConfig: *nr.configs.MainP2pConfig, FullArchiveP2pConfig: *nr.configs.FullArchiveP2pConfig, + LightClientP2pConfig: *nr.configs.LightClientP2pConfig, MainConfig: *nr.configs.GeneralConfig, RatingsConfig: *nr.configs.RatingsConfig, StatusHandler: statusCoreComponents.AppStatusHandler(), @@ -1424,7 +1426,7 @@ func (nr *nodeRunner) CreateManagedNetworkComponents( Syncer: coreComponents.SyncTimer(), PreferredPeersSlices: nr.configs.PreferencesConfig.Preferences.PreferredConnections, BootstrapWaitTime: common.TimeToWaitForP2PBootstrap, - NodeOperationMode: common.NormalOperation, + NodeOperationModes: []common.NodeOperation{common.NormalOperation}, ConnectionWatcherType: nr.configs.PreferencesConfig.Preferences.ConnectionWatcherType, CryptoComponents: cryptoComponents, } @@ -1432,7 +1434,13 @@ func (nr *nodeRunner) CreateManagedNetworkComponents( networkComponentsFactoryArgs.BootstrapWaitTime = 0 } if nr.configs.PreferencesConfig.Preferences.FullArchive { - networkComponentsFactoryArgs.NodeOperationMode = common.FullArchiveMode + networkComponentsFactoryArgs.NodeOperationModes = []common.NodeOperation{common.FullArchiveMode} + } + if nr.configs.PreferencesConfig.Preferences.LightClient { + networkComponentsFactoryArgs.NodeOperationModes = append(networkComponentsFactoryArgs.NodeOperationModes, common.LightClientMode) + } + if nr.configs.PreferencesConfig.Preferences.LightClientSupplier { + networkComponentsFactoryArgs.NodeOperationModes = append(networkComponentsFactoryArgs.NodeOperationModes, common.LightClientSupplierMode) } networkComponentsFactory, err := networkComp.NewNetworkComponentsFactory(networkComponentsFactoryArgs) diff --git a/p2p/constants.go b/p2p/constants.go index 8a6db9caeb4..cf3b29f8e09 100644 --- a/p2p/constants.go +++ b/p2p/constants.go @@ -13,6 +13,9 @@ const MainNetwork NetworkType = "main" // FullArchiveNetwork defines the full archive network const FullArchiveNetwork NetworkType = "full archive" +// LightClientNetwork defines the light clients network +const LightClientNetwork NetworkType = "light client" + // ListsSharder is the variant that uses lists const ListsSharder = p2p.ListsSharder diff --git a/testscommon/components/components.go b/testscommon/components/components.go index 0e3dcc14cd1..8e89c4ffd9d 100644 --- a/testscommon/components/components.go +++ b/testscommon/components/components.go @@ -8,6 +8,10 @@ import ( "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/outport" + logger "github.com/multiversx/mx-chain-logger-go" + wasmConfig "github.com/multiversx/mx-chain-vm-go/config" + "github.com/stretchr/testify/require" + "github.com/multiversx/mx-chain-go/common" commonFactory "github.com/multiversx/mx-chain-go/common/factory" "github.com/multiversx/mx-chain-go/config" @@ -41,9 +45,6 @@ import ( statusHandlerMock "github.com/multiversx/mx-chain-go/testscommon/statusHandler" "github.com/multiversx/mx-chain-go/testscommon/storage" "github.com/multiversx/mx-chain-go/trie" - logger "github.com/multiversx/mx-chain-logger-go" - wasmConfig "github.com/multiversx/mx-chain-vm-go/config" - "github.com/stretchr/testify/require" ) var log = logger.GetOrCreate("componentsMock") @@ -308,11 +309,11 @@ func GetNetworkFactoryArgs() networkComp.NetworkComponentsFactoryArgs { cryptoCompMock := GetDefaultCryptoComponents() return networkComp.NetworkComponentsFactoryArgs{ - MainP2pConfig: p2pCfg, - NodeOperationMode: common.NormalOperation, - MainConfig: mainConfig, - StatusHandler: appStatusHandler, - Marshalizer: &mock.MarshalizerMock{}, + MainP2pConfig: p2pCfg, + NodeOperationModes: []common.NodeOperation{common.NormalOperation}, + MainConfig: mainConfig, + StatusHandler: appStatusHandler, + Marshalizer: &mock.MarshalizerMock{}, RatingsConfig: config.RatingsConfig{ General: config.General{}, ShardChain: config.ShardChain{},