diff --git a/multiversx_sdk_cli/localnet/config_default.py b/multiversx_sdk_cli/localnet/config_default.py index 49a1ca42..c45fea09 100644 --- a/multiversx_sdk_cli/localnet/config_default.py +++ b/multiversx_sdk_cli/localnet/config_default.py @@ -15,6 +15,9 @@ genesis_delay_seconds=10, rounds_per_epoch=100, round_duration_milliseconds=6000, + # For the purpose of the localnet, we'll have 3x for Supernova (by default). + rounds_per_epoch_in_supernova=300, + round_duration_milliseconds_in_supernova=2000, ) software = Software( diff --git a/multiversx_sdk_cli/localnet/config_general.py b/multiversx_sdk_cli/localnet/config_general.py index 03b1c943..f842206b 100644 --- a/multiversx_sdk_cli/localnet/config_general.py +++ b/multiversx_sdk_cli/localnet/config_general.py @@ -10,11 +10,15 @@ def __init__( genesis_delay_seconds: int, rounds_per_epoch: int, round_duration_milliseconds: int, + rounds_per_epoch_in_supernova: int, + round_duration_milliseconds_in_supernova: int ): - self.log_level: str = log_level - self.genesis_delay_seconds: int = genesis_delay_seconds - self.rounds_per_epoch: int = rounds_per_epoch - self.round_duration_milliseconds: int = round_duration_milliseconds + self.log_level = log_level + self.genesis_delay_seconds = genesis_delay_seconds + self.rounds_per_epoch = rounds_per_epoch + self.round_duration_milliseconds = round_duration_milliseconds + self.rounds_per_epoch_in_supernova = rounds_per_epoch_in_supernova + self.round_duration_milliseconds_in_supernova = round_duration_milliseconds_in_supernova def get_name(self) -> str: return "general" @@ -24,3 +28,5 @@ def _do_override(self, other: Dict[str, Any]): self.genesis_delay_seconds = other.get("genesis_delay_seconds", self.genesis_delay_seconds) self.rounds_per_epoch = other.get("rounds_per_epoch", self.rounds_per_epoch) self.round_duration_milliseconds = other.get("round_duration_milliseconds", self.round_duration_milliseconds) + self.rounds_per_epoch_in_supernova = other.get("rounds_per_epoch_in_supernova", self.rounds_per_epoch_in_supernova) + self.round_duration_milliseconds_in_supernova = other.get("round_duration_milliseconds_in_supernova", self.round_duration_milliseconds_in_supernova) diff --git a/multiversx_sdk_cli/localnet/constants.py b/multiversx_sdk_cli/localnet/constants.py index 5e5bec4f..a8939efc 100644 --- a/multiversx_sdk_cli/localnet/constants.py +++ b/multiversx_sdk_cli/localnet/constants.py @@ -6,3 +6,8 @@ FILE_MODE_EXECUTABLE = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH ) + +# See https://github.com/multiversx/mx-chain-go/blob/master/cmd/node/config/config.toml. +ROUNDS_PER_EPOCH_TO_MIN_ROUNDS_BETWEEN_EPOCHS_RATIO = 4 +# See https://github.com/multiversx/mx-chain-go/blob/master/cmd/node/config/enableRounds.toml. +NUM_ROUNDS_BETWEEN_SUPERNOVA_ACTIVATION_EPOCH_AND_ACTIVATION_ROUND = 20 diff --git a/multiversx_sdk_cli/localnet/node_config_toml.py b/multiversx_sdk_cli/localnet/node_config_toml.py index bfd59d4a..38b6e510 100644 --- a/multiversx_sdk_cli/localnet/node_config_toml.py +++ b/multiversx_sdk_cli/localnet/node_config_toml.py @@ -1,12 +1,18 @@ from typing import Any, Dict from multiversx_sdk_cli.localnet.config_root import ConfigRoot +from multiversx_sdk_cli.localnet.constants import ( + NUM_ROUNDS_BETWEEN_SUPERNOVA_ACTIVATION_EPOCH_AND_ACTIVATION_ROUND, + ROUNDS_PER_EPOCH_TO_MIN_ROUNDS_BETWEEN_EPOCHS_RATIO, +) from multiversx_sdk_cli.localnet.nodes_setup_json import CHAIN_ID ConfigDict = Dict[str, Any] -def patch_config(data: ConfigDict, config: ConfigRoot): +def patch_config(data: ConfigDict, config: ConfigRoot, enable_epochs_config: ConfigDict): + supernova_activation_epoch = enable_epochs_config["EnableEpochs"].get("SupernovaEnableEpoch", None) + data["GeneralSettings"]["ChainID"] = CHAIN_ID # "--operation-mode=historical-balances" is not available for nodes, @@ -18,33 +24,58 @@ def patch_config(data: ConfigDict, config: ConfigRoot): data["StoragePruning"]["ObserverCleanOldEpochsData"] = False data["StoragePruning"]["AccountsTrieCleanOldEpochsData"] = False - # Make epochs shorter - epoch_start_config: ConfigDict = dict() - epoch_start_config["RoundsPerEpoch"] = config.general.rounds_per_epoch - epoch_start_config["MinRoundsBetweenEpochs"] = int(config.general.rounds_per_epoch / 4) + # Some time after the release of Supernova, we should drop this custom (and somewhat cumbersome) logic. + if supernova_activation_epoch is None: + # Before Supernova (as software version, not as "era after activation"), + # we alter epoch duration by adjusting "RoundsPerEpoch" and "MinRoundsBetweenEpochs" in section "EpochStartConfig". + # In a Supernova-aware node configuration, these entries do not exist anymore (see "ChainParametersByEpoch"). + epoch_start_config: ConfigDict = dict() + epoch_start_config["RoundsPerEpoch"] = config.general.rounds_per_epoch + epoch_start_config["MinRoundsBetweenEpochs"] = int( + config.general.rounds_per_epoch / ROUNDS_PER_EPOCH_TO_MIN_ROUNDS_BETWEEN_EPOCHS_RATIO + ) + + data["EpochStartConfig"].update(epoch_start_config) - data["EpochStartConfig"].update(epoch_start_config) data["WebServerAntiflood"]["VmQueryDelayAfterStartInSec"] = 30 # Always use the latest VM data["VirtualMachine"]["Execution"]["WasmVMVersions"] = [{"StartEpoch": 0, "Version": "*"}] data["VirtualMachine"]["Querying"]["WasmVMVersions"] = [{"StartEpoch": 0, "Version": "*"}] - # Adjust "ChainParametersByEpoch" (for Andromeda) + # Adjust "ChainParametersByEpoch" chain_parameters_by_epoch = data["GeneralSettings"].get("ChainParametersByEpoch", []) - if chain_parameters_by_epoch: - # For convenience, keep a single entry ... - chain_parameters_by_epoch.clear() - chain_parameters_by_epoch.append({}) + for item in chain_parameters_by_epoch: + enable_epoch = item["EnableEpoch"] + + is_supernova_enabled = supernova_activation_epoch is not None and enable_epoch >= supernova_activation_epoch + if is_supernova_enabled: + item["RoundDuration"] = config.general.round_duration_milliseconds_in_supernova + item["RoundsPerEpoch"] = config.general.rounds_per_epoch_in_supernova + item["MinRoundsBetweenEpochs"] = int( + config.general.rounds_per_epoch_in_supernova / ROUNDS_PER_EPOCH_TO_MIN_ROUNDS_BETWEEN_EPOCHS_RATIO + ) + else: + item["RoundDuration"] = config.general.round_duration_milliseconds + item["RoundsPerEpoch"] = config.general.rounds_per_epoch + item["MinRoundsBetweenEpochs"] = int( + config.general.rounds_per_epoch / ROUNDS_PER_EPOCH_TO_MIN_ROUNDS_BETWEEN_EPOCHS_RATIO + ) + + item["ShardConsensusGroupSize"] = config.shards.consensus_size + item["ShardMinNumNodes"] = config.shards.num_validators_per_shard + item["MetachainConsensusGroupSize"] = config.metashard.consensus_size + item["MetachainMinNumNodes"] = config.metashard.num_validators - # ... and set the activation epoch to 0 - chain_parameters_by_epoch[0]["EnableEpoch"] = 0 - chain_parameters_by_epoch[0]["RoundDuration"] = config.general.round_duration_milliseconds - chain_parameters_by_epoch[0]["ShardConsensusGroupSize"] = config.shards.consensus_size - chain_parameters_by_epoch[0]["ShardMinNumNodes"] = config.shards.num_validators_per_shard - chain_parameters_by_epoch[0]["MetachainConsensusGroupSize"] = config.metashard.consensus_size - chain_parameters_by_epoch[0]["MetachainMinNumNodes"] = config.metashard.num_validators + # Adjust "Versions" (of blocks) + versions_by_epoch = data["Versions"].get("VersionsByEpochs", []) + + for item in versions_by_epoch: + enable_epoch = item["StartEpoch"] + + if enable_epoch == supernova_activation_epoch: + item["StartRound"] = _compute_supernova_activation_round(config, supernova_activation_epoch) def patch_api(data: ConfigDict, config: ConfigRoot): @@ -55,10 +86,6 @@ def patch_api(data: ConfigDict, config: ConfigRoot): def patch_enable_epochs(data: ConfigDict, config: ConfigRoot): enable_epochs = data["EnableEpochs"] - enable_epochs["SCDeployEnableEpoch"] = 0 - enable_epochs["BuiltInFunctionsEnableEpoch"] = 0 - enable_epochs["RelayedTransactionsEnableEpoch"] = 0 - enable_epochs["PenalizedTooMuchGasEnableEpoch"] = 0 enable_epochs["AheadOfTimeGasUsageEnableEpoch"] = 0 enable_epochs["GasPriceModifierEnableEpoch"] = 0 enable_epochs["RepairCallbackEnableEpoch"] = 0 @@ -69,15 +96,34 @@ def patch_enable_epochs(data: ConfigDict, config: ConfigRoot): enable_epochs["ESDTMultiTransferEnableEpoch"] = 0 enable_epochs["GlobalMintBurnDisableEpoch"] = 0 enable_epochs["ESDTTransferRoleEnableEpoch"] = 0 - enable_epochs["BuiltInFunctionOnMetaEnableEpoch"] = 0 enable_epochs["MultiESDTTransferFixOnCallBackOnEnableEpoch"] = 0 enable_epochs["ESDTNFTCreateOnMultiShard"] = 0 enable_epochs["MetaESDTSetEnableEpoch"] = 0 enable_epochs["DelegationManagerEnableEpoch"] = 0 + # Adjust "MaxNumNodes": max_nodes_change_enable_epoch = enable_epochs["MaxNodesChangeEnableEpoch"] last_entry = max_nodes_change_enable_epoch[-1] penultimate_entry = max_nodes_change_enable_epoch[-2] last_entry["MaxNumNodes"] = ( penultimate_entry["MaxNumNodes"] - (config.shards.num_shards + 1) * penultimate_entry["NodesToShufflePerShard"] ) + + +def patch_enable_rounds(data: ConfigDict, config: ConfigRoot, enable_epochs_config: ConfigDict): + supernova_activation_epoch = enable_epochs_config["EnableEpochs"].get("SupernovaEnableEpoch", None) + + activations = data["RoundActivations"] + supernova_entry = activations.get("SupernovaEnableRound") + + if supernova_entry and supernova_activation_epoch is not None: + supernova_computed_activation_round = _compute_supernova_activation_round(config, supernova_activation_epoch) + supernova_entry["Round"] = str(supernova_computed_activation_round) + + +def _compute_supernova_activation_round(config: ConfigRoot, supernova_activation_epoch: int) -> int: + # Epochs are zero-indexed. + return ( + config.general.rounds_per_epoch * supernova_activation_epoch + + NUM_ROUNDS_BETWEEN_SUPERNOVA_ACTIVATION_EPOCH_AND_ACTIVATION_ROUND + ) diff --git a/multiversx_sdk_cli/localnet/nodes_setup_json.py b/multiversx_sdk_cli/localnet/nodes_setup_json.py index b99a4c2a..c0900a25 100644 --- a/multiversx_sdk_cli/localnet/nodes_setup_json.py +++ b/multiversx_sdk_cli/localnet/nodes_setup_json.py @@ -26,14 +26,5 @@ def build(config: ConfigRoot) -> Any: return { "startTime": config.genesis_time(), - "roundDuration": config.general.round_duration_milliseconds, - "consensusGroupSize": config.shards.consensus_size, - "minNodesPerShard": config.shards.consensus_size, - "metaChainConsensusGroupSize": config.metashard.consensus_size, - "metaChainMinNodes": config.metashard.num_validators, - "hysteresis": 0, - "adaptivity": False, - "chainID": CHAIN_ID, - "minTransactionVersion": 1, "initialNodes": initial_nodes, } diff --git a/multiversx_sdk_cli/localnet/step_config.py b/multiversx_sdk_cli/localnet/step_config.py index d864d5cb..c1ca9fa5 100644 --- a/multiversx_sdk_cli/localnet/step_config.py +++ b/multiversx_sdk_cli/localnet/step_config.py @@ -101,24 +101,28 @@ def copy_validator_keys(config: ConfigRoot): def patch_node_config(config: ConfigRoot): for node_config in config.all_nodes_config_folders(): node_config_file = node_config / "config.toml" - data = utils.read_toml_file(node_config_file) - node_config_toml.patch_config(data, config) - utils.write_toml_file(node_config_file, data) - api_config_file = node_config / "api.toml" - data = utils.read_toml_file(api_config_file) - node_config_toml.patch_api(data, config) - utils.write_toml_file(api_config_file, data) - enable_epochs_config_file = node_config / "enableEpochs.toml" - data = utils.read_toml_file(enable_epochs_config_file) - node_config_toml.patch_enable_epochs(data, config) - utils.write_toml_file(enable_epochs_config_file, data) - + enable_rounds_config_file = node_config / "enableRounds.toml" genesis_smart_contracts_file = node_config / "genesisSmartContracts.json" - data = utils.read_json_file(genesis_smart_contracts_file) - genesis_smart_contracts_json.patch(data, config) - utils.write_json_file(genesis_smart_contracts_file, data) + + node_config_data = utils.read_toml_file(node_config_file) + api_config_data = utils.read_toml_file(api_config_file) + enable_epochs_config_data = utils.read_toml_file(enable_epochs_config_file) + enable_rounds_config_data = utils.read_toml_file(enable_rounds_config_file) + genesis_smart_contracts_data = utils.read_json_file(genesis_smart_contracts_file) + + node_config_toml.patch_config(node_config_data, config, enable_epochs_config_data) + node_config_toml.patch_api(api_config_data, config) + node_config_toml.patch_enable_epochs(enable_epochs_config_data, config) + node_config_toml.patch_enable_rounds(enable_rounds_config_data, config, enable_epochs_config_data) + genesis_smart_contracts_json.patch(genesis_smart_contracts_data, config) + + utils.write_toml_file(node_config_file, node_config_data) + utils.write_toml_file(api_config_file, api_config_data) + utils.write_toml_file(enable_epochs_config_file, enable_epochs_config_data) + utils.write_toml_file(enable_rounds_config_file, enable_rounds_config_data) + utils.write_json_file(genesis_smart_contracts_file, genesis_smart_contracts_data) def copy_config_to_seednode(config: ConfigRoot): diff --git a/multiversx_sdk_cli/tests/test_testnet.py b/multiversx_sdk_cli/tests/test_testnet.py index 999219b5..5b9ab3ca 100644 --- a/multiversx_sdk_cli/tests/test_testnet.py +++ b/multiversx_sdk_cli/tests/test_testnet.py @@ -14,6 +14,8 @@ def test_override_config() -> None: # Check a few default values assert config.general.rounds_per_epoch == 100 assert config.general.round_duration_milliseconds == 6000 + assert config.general.rounds_per_epoch_in_supernova == 300 + assert config.general.round_duration_milliseconds_in_supernova == 2000 assert config.metashard.consensus_size == 1 assert config.networking.port_proxy == 7950 assert config.software.mx_chain_go.resolution == SoftwareResolution.Remote @@ -27,6 +29,8 @@ def test_override_config() -> None: config_patch["general"] = { "rounds_per_epoch": 200, "round_duration_milliseconds": 4000, + "rounds_per_epoch_in_supernova": 400, + "round_duration_milliseconds_in_supernova": 1000, } config_patch["metashard"] = { "consensus_size": 2, @@ -43,6 +47,8 @@ def test_override_config() -> None: # Check the overridden values assert config.general.rounds_per_epoch == 200 assert config.general.round_duration_milliseconds == 4000 + assert config.general.rounds_per_epoch_in_supernova == 400 + assert config.general.round_duration_milliseconds_in_supernova == 1000 assert config.metashard.consensus_size == 2 assert config.networking.port_proxy == 7951 assert config.software.mx_chain_go.resolution == SoftwareResolution.Remote diff --git a/pyproject.toml b/pyproject.toml index 29236351..bb92d3d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "multiversx-sdk-cli" -version = "11.2.3" +version = "11.3.0" authors = [ { name="MultiversX" }, ]