diff --git a/multiversx_sdk_cli/cli_delegation.py b/multiversx_sdk_cli/cli_delegation.py index 3d76b348..7d936c18 100644 --- a/multiversx_sdk_cli/cli_delegation.py +++ b/multiversx_sdk_cli/cli_delegation.py @@ -41,7 +41,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # remove nodes sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "remove-nodes", "Remove nodes must be called by the contract owner") - sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes") + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") + sub.add_argument("--validators-file", help="a JSON file describing the Nodes") sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") _add_common_arguments(args, sub) sub.set_defaults(func=remove_nodes) @@ -49,7 +50,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # stake nodes sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "stake-nodes", "Stake nodes must be called by the contract owner") - sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes") + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") + sub.add_argument("--validators-file", help="a JSON file describing the Nodes") sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") _add_common_arguments(args, sub) sub.set_defaults(func=stake_nodes) @@ -57,7 +59,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # unbond nodes sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unbond-nodes", "Unbond nodes must be called by the contract owner") - sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes") + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") + sub.add_argument("--validators-file", help="a JSON file describing the Nodes") sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") _add_common_arguments(args, sub) sub.set_defaults(func=unbond_nodes) @@ -65,7 +68,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # unstake nodes sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unstake-nodes", "Unstake nodes must be called by the contract owner") - sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes") + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") + sub.add_argument("--validators-file", help="a JSON file describing the Nodes") sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") _add_common_arguments(args, sub) sub.set_defaults(func=unstake_nodes) @@ -73,7 +77,8 @@ def setup_parser(args: List[str], subparsers: Any) -> Any: # unjail nodes sub = cli_shared.add_command_subparser(subparsers, "staking-provider", "unjail-nodes", "Unjail nodes must be called by the contract owner") - sub.add_argument("--bls-keys", required=True, help="a list with the bls keys of the nodes") + sub.add_argument("--bls-keys", help="a list with the bls keys of the nodes") + sub.add_argument("--validators-file", help="a JSON file describing the Nodes") sub.add_argument("--delegation-contract", required=True, help="address of the delegation contract") _add_common_arguments(args, sub) sub.set_defaults(func=unjail_nodes) @@ -182,6 +187,7 @@ def add_new_nodes(args: Any): def remove_nodes(args: Any): + _check_if_either_bls_keys_or_validators_file_are_provided(args) cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) @@ -196,6 +202,7 @@ def remove_nodes(args: Any): def stake_nodes(args: Any): + _check_if_either_bls_keys_or_validators_file_are_provided(args) cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) @@ -209,7 +216,16 @@ def stake_nodes(args: Any): cli_shared.send_or_simulate(tx, args) +def _check_if_either_bls_keys_or_validators_file_are_provided(args: Any): + bls_keys = args.bls_keys + validators_file = args.validators_file + + if not bls_keys and not validators_file: + raise errors.BadUsage("No bls keys or validators file provided. Use either `--bls-keys` or `--validators-file`") + + def unbond_nodes(args: Any): + _check_if_either_bls_keys_or_validators_file_are_provided(args) cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) @@ -224,6 +240,7 @@ def unbond_nodes(args: Any): def unstake_nodes(args: Any): + _check_if_either_bls_keys_or_validators_file_are_provided(args) cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) @@ -238,6 +255,7 @@ def unstake_nodes(args: Any): def unjail_nodes(args: Any): + _check_if_either_bls_keys_or_validators_file_are_provided(args) cli_shared.check_guardian_and_options_args(args) cli_shared.check_broadcast_args(args) cli_shared.prepare_chain_id_in_args(args) diff --git a/multiversx_sdk_cli/delegation/staking_provider.py b/multiversx_sdk_cli/delegation/staking_provider.py index cbcb6160..6c75bf68 100644 --- a/multiversx_sdk_cli/delegation/staking_provider.py +++ b/multiversx_sdk_cli/delegation/staking_provider.py @@ -82,7 +82,8 @@ def prepare_transaction_for_adding_nodes(self, owner: IAccount, args: Any) -> IT def prepare_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys = self._parse_public_bls_keys(args.bls_keys) + + public_keys = self._load_validators_public_keys(args) tx = self._factory.create_transaction_for_removing_nodes( sender=owner.address, @@ -102,7 +103,8 @@ def prepare_transaction_for_removing_nodes(self, owner: IAccount, args: Any) -> def prepare_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys = self._parse_public_bls_keys(args.bls_keys) + + public_keys = self._load_validators_public_keys(args) tx = self._factory.create_transaction_for_staking_nodes( sender=owner.address, @@ -122,7 +124,8 @@ def prepare_transaction_for_staking_nodes(self, owner: IAccount, args: Any) -> I def prepare_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys = self._parse_public_bls_keys(args.bls_keys) + + public_keys = self._load_validators_public_keys(args) tx = self._factory.create_transaction_for_unbonding_nodes( sender=owner.address, @@ -142,7 +145,8 @@ def prepare_transaction_for_unbonding_nodes(self, owner: IAccount, args: Any) -> def prepare_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys = self._parse_public_bls_keys(args.bls_keys) + + public_keys = self._load_validators_public_keys(args) tx = self._factory.create_transaction_for_unstaking_nodes( sender=owner.address, @@ -162,7 +166,8 @@ def prepare_transaction_for_unstaking_nodes(self, owner: IAccount, args: Any) -> def prepare_transaction_for_unjailing_nodes(self, owner: IAccount, args: Any) -> ITransaction: delegation_contract = Address.new_from_bech32(args.delegation_contract) - public_keys = self._parse_public_bls_keys(args.bls_keys) + + public_keys = self._load_validators_public_keys(args) tx = self._factory.create_transaction_for_unjailing_nodes( sender=owner.address, @@ -293,6 +298,14 @@ def prepare_transaction_for_setting_metadata(self, owner: IAccount, args: Any) - return tx + def _load_validators_public_keys(self, args: Any) -> List[ValidatorPublicKey]: + if args.bls_keys: + return self._parse_public_bls_keys(args.bls_keys) + + validators_file_path = Path(args.validators_file).expanduser() + validators_file = ValidatorsFile(validators_file_path) + return validators_file.load_public_keys() + def _parse_public_bls_keys(self, public_bls_keys: str) -> List[ValidatorPublicKey]: keys = public_bls_keys.split(",") validator_public_keys: List[ValidatorPublicKey] = [] diff --git a/multiversx_sdk_cli/tests/test_cli_staking_provider.py b/multiversx_sdk_cli/tests/test_cli_staking_provider.py index 3b3f8a10..f785b8dd 100644 --- a/multiversx_sdk_cli/tests/test_cli_staking_provider.py +++ b/multiversx_sdk_cli/tests/test_cli_staking_provider.py @@ -9,6 +9,7 @@ first_bls_key = "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d" second_bls_key = "1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" +validators_file = parent / "testdata" / "validators_file.json" def test_create_new_delegation_contract(capsys: Any): @@ -53,7 +54,7 @@ def test_add_nodes(capsys: Any): assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" -def test_remove_nodes(capsys: Any): +def test_remove_nodes_with_bls_keys(capsys: Any): main([ "staking-provider", "remove-nodes", "--bls-keys", f"{first_bls_key},{second_bls_key}", @@ -72,7 +73,45 @@ def test_remove_nodes(capsys: Any): assert transaction["gasLimit"] == 13645500 -def test_stake_nodes(capsys: Any): +def test_remove_nodes_with_validators_file(capsys: Any): + main([ + "staking-provider", "remove-nodes", + "--validators-file", str(validators_file), + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--chain", "T", + "--nonce", "7", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "removeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 13645500 + + +def test_stake_nodes_with_bls_keys(capsys: Any): + main([ + "staking-provider", "stake-nodes", + "--validators-file", str(validators_file), + "--delegation-contract", "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh", + "--pem", str(alice), + "--chain", "T", + "--nonce", "7", "--estimate-gas" + ]) + tx = get_transaction(capsys) + data = tx["emittedTransactionData"] + transaction = tx["emittedTransaction"] + + assert data == "stakeNodes@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d" + assert transaction["sender"] == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + assert transaction["receiver"] == "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqthllllsy5r6rh" + assert transaction["gasLimit"] == 18644000 + + +def test_stake_nodes_with_validators_file(capsys: Any): main([ "staking-provider", "stake-nodes", "--bls-keys", f"{first_bls_key},{second_bls_key}", diff --git a/multiversx_sdk_cli/tests/testdata/validator_01.pem b/multiversx_sdk_cli/tests/testdata/validator_01.pem new file mode 100644 index 00000000..32f62751 --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validator_01.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 +MWU5NmU1NGIyMThkNDQyYQ== +-----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- diff --git a/multiversx_sdk_cli/tests/testdata/validator_02.pem b/multiversx_sdk_cli/tests/testdata/validator_02.pem new file mode 100644 index 00000000..6f700ebb --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validator_02.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- +MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 +YjU4NmUyODM5YjVlNTI2Mw== +-----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- diff --git a/multiversx_sdk_cli/tests/testdata/validators_file.json b/multiversx_sdk_cli/tests/testdata/validators_file.json new file mode 100644 index 00000000..f9fedd19 --- /dev/null +++ b/multiversx_sdk_cli/tests/testdata/validators_file.json @@ -0,0 +1,10 @@ +{ + "validators": [ + { + "pemFile": "validator_01.pem" + }, + { + "pemFile": "validator_02.pem" + } + ] +} diff --git a/multiversx_sdk_cli/validators/validators_file.py b/multiversx_sdk_cli/validators/validators_file.py index 8da16bd5..4a664aac 100644 --- a/multiversx_sdk_cli/validators/validators_file.py +++ b/multiversx_sdk_cli/validators/validators_file.py @@ -1,8 +1,9 @@ import json from pathlib import Path -from typing import List +from typing import Dict, List from multiversx_sdk_wallet import ValidatorSigner +from multiversx_sdk_wallet.validator_keys import ValidatorPublicKey from multiversx_sdk_wallet.validator_pem import ValidatorPEM from multiversx_sdk_cli import guards @@ -23,17 +24,28 @@ def get_validators_list(self): def load_signers(self) -> List[ValidatorSigner]: signers: List[ValidatorSigner] = [] for validator in self.get_validators_list(): - # Get path of "pemFile", make it absolute - validator_pem = Path(validator.get("pemFile")).expanduser() - validator_pem = validator_pem if validator_pem.is_absolute() else self.validators_file_path.parent / validator_pem - - pem_file = ValidatorPEM.from_file(validator_pem) - + pem_file = self._load_validator_pem(validator) validator_signer = ValidatorSigner(pem_file.secret_key) signers.append(validator_signer) return signers + def load_public_keys(self) -> List[ValidatorPublicKey]: + public_keys: List[ValidatorPublicKey] = [] + + for validator in self.get_validators_list(): + pem_file = self._load_validator_pem(validator) + public_keys.append(pem_file.secret_key.generate_public_key()) + + return public_keys + + def _load_validator_pem(self, validator: Dict[str, str]) -> ValidatorPEM: + # Get path of "pemFile", make it absolute + validator_pem = Path(validator.get("pemFile", "")).expanduser() + validator_pem = validator_pem if validator_pem.is_absolute() else self.validators_file_path.parent / validator_pem + + return ValidatorPEM.from_file(validator_pem) + def _read_json_file_validators(self): val_file = self.validators_file_path.expanduser() guards.is_file(val_file) diff --git a/pyproject.toml b/pyproject.toml index f0a6310f..1940090a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "multiversx-sdk-cli" -version = "9.4.1" +version = "9.5.0" authors = [ { name="MultiversX" }, ]