Skip to content

Commit

Permalink
Merge pull request #409 from multiversx/stake-nodes-with-validators-file
Browse files Browse the repository at this point in the history
Use validators file to stake, unstake, etc
  • Loading branch information
popenta authored Feb 8, 2024
2 parents b2b07a9 + 70b7b02 commit 26f5889
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 20 deletions.
28 changes: 23 additions & 5 deletions multiversx_sdk_cli/cli_delegation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,44 @@ 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)

# 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)

# 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)

# 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)

# 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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
23 changes: 18 additions & 5 deletions multiversx_sdk_cli/delegation/staking_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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] = []
Expand Down
43 changes: 41 additions & 2 deletions multiversx_sdk_cli/tests/test_cli_staking_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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}",
Expand All @@ -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}",
Expand Down
4 changes: 4 additions & 0 deletions multiversx_sdk_cli/tests/testdata/validator_01.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d-----
N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2
MWU5NmU1NGIyMThkNDQyYQ==
-----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d-----
4 changes: 4 additions & 0 deletions multiversx_sdk_cli/tests/testdata/validator_02.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d-----
MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1
YjU4NmUyODM5YjVlNTI2Mw==
-----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d-----
10 changes: 10 additions & 0 deletions multiversx_sdk_cli/tests/testdata/validators_file.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"validators": [
{
"pemFile": "validator_01.pem"
},
{
"pemFile": "validator_02.pem"
}
]
}
26 changes: 19 additions & 7 deletions multiversx_sdk_cli/validators/validators_file.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
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 = "9.4.1"
version = "9.5.0"
authors = [
{ name="MultiversX" },
]
Expand Down

0 comments on commit 26f5889

Please sign in to comment.