Skip to content
3 changes: 3 additions & 0 deletions multiversx_sdk_cli/localnet/config_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we have the actual duration of the round (600ms)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question!

I've chosen 2 seconds by default in order to demand less resources from the host machine (development machine).

All good?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Fine with me. We should at least document that the localnet starts with a roundtime of 2s and we should show people where to change in case they want to run a 600ms round, replicating mainnet config.

)

software = Software(
Expand Down
14 changes: 10 additions & 4 deletions multiversx_sdk_cli/localnet/config_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
5 changes: 5 additions & 0 deletions multiversx_sdk_cli/localnet/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

92 changes: 69 additions & 23 deletions multiversx_sdk_cli/localnet/node_config_toml.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 ...
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tricky. Changed to use the whole collection of ChainParametersByEpoch entries.

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
Comment on lines +54 to +55
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Once Supernova is active, we use the new localnet configuration entries: round_duration_milliseconds_in_supernova and 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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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



def patch_api(data: ConfigDict, config: ConfigRoot):
Expand All @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

enable_epochs["BuiltInFunctionsEnableEpoch"] = 0
enable_epochs["RelayedTransactionsEnableEpoch"] = 0
enable_epochs["PenalizedTooMuchGasEnableEpoch"] = 0
enable_epochs["AheadOfTimeGasUsageEnableEpoch"] = 0
enable_epochs["GasPriceModifierEnableEpoch"] = 0
enable_epochs["RepairCallbackEnableEpoch"] = 0
Expand All @@ -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):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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
)
9 changes: 0 additions & 9 deletions multiversx_sdk_cli/localnet/nodes_setup_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,5 @@ def build(config: ConfigRoot) -> Any:

return {
"startTime": config.genesis_time(),
"roundDuration": config.general.round_duration_milliseconds,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"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,
}
34 changes: 19 additions & 15 deletions multiversx_sdk_cli/localnet/step_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Trivial refactoring, so that we can read & pass supernova_activation_epoch to patch_config more easily.

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):
Expand Down
6 changes: 6 additions & 0 deletions multiversx_sdk_cli/tests/test_testnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "multiversx-sdk-cli"
version = "11.2.3"
version = "11.3.0"
authors = [
{ name="MultiversX" },
]
Expand Down
Loading