diff --git a/.gitignore b/.gitignore index 1483aa49c..d36e2b95c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,6 @@ neofs-node neofs-rest-gw neofs-s3-authmate neofs-s3-gw +neofs-contract neofs_env_*.zip temp_files.zip diff --git a/neofs-testlib/neofs_testlib/cli/neogo/contract.py b/neofs-testlib/neofs_testlib/cli/neogo/contract.py index 516dcecab..58438428d 100644 --- a/neofs-testlib/neofs_testlib/cli/neogo/contract.py +++ b/neofs-testlib/neofs_testlib/cli/neogo/contract.py @@ -39,10 +39,10 @@ def contract_compile( def deploy( self, - address: str, input_file: str, manifest: str, rpc_endpoint: str, + address: Optional[str] = None, sysgas: Optional[float] = None, wallet: Optional[str] = None, wallet_config: Optional[str] = None, @@ -51,6 +51,7 @@ def deploy( out: Optional[str] = None, force: bool = False, timeout: int = 10, + post_data: Optional[str] = None, ) -> CommandResult: """Deploy a smart contract (.nef with description) @@ -76,8 +77,11 @@ def deploy( """ assert bool(wallet) ^ bool(wallet_config), self.WALLET_SOURCE_ERROR_MSG exec_param = { - param: param_value for param, param_value in locals().items() if param not in ["self", "wallet_password"] + param: param_value + for param, param_value in locals().items() + if param not in ["self", "wallet_password", "input_file"] } + exec_param["in"] = input_file exec_param["timeout"] = f"{timeout}s" if wallet_password is not None: @@ -323,9 +327,13 @@ def calc_hash( Returns: Command's result. """ + exec_param = { + param: param_value for param, param_value in locals().items() if param not in ["self", "input_file"] + } + exec_param["in"] = input_file return self._execute( "contract calc-hash", - **{param: param_value for param, param_value in locals().items() if param not in ["self"]}, + **exec_param, ) def add_group( diff --git a/neofs-testlib/neofs_testlib/cli/neogo/go.py b/neofs-testlib/neofs_testlib/cli/neogo/go.py index 5f216ce9a..44630ce9f 100644 --- a/neofs-testlib/neofs_testlib/cli/neogo/go.py +++ b/neofs-testlib/neofs_testlib/cli/neogo/go.py @@ -6,6 +6,7 @@ from neofs_testlib.cli.neogo.nep17 import NeoGoNep17 from neofs_testlib.cli.neogo.node import NeoGoNode from neofs_testlib.cli.neogo.query import NeoGoQuery +from neofs_testlib.cli.neogo.util import NeoGoUtil from neofs_testlib.cli.neogo.version import NeoGoVersion from neofs_testlib.cli.neogo.wallet import NeoGoWallet from neofs_testlib.shell import Shell @@ -20,6 +21,7 @@ class NeoGo: query: Optional[NeoGoQuery] = None version: Optional[NeoGoVersion] = None wallet: Optional[NeoGoWallet] = None + util: Optional[NeoGoUtil] = None def __init__( self, @@ -35,3 +37,4 @@ def __init__( self.query = NeoGoQuery(shell, neo_go_exec_path, config_path=config_path) self.version = NeoGoVersion(shell, neo_go_exec_path, config_path=config_path) self.wallet = NeoGoWallet(shell, neo_go_exec_path, config_path=config_path) + self.util = NeoGoUtil(shell, neo_go_exec_path, config_path=config_path) diff --git a/neofs-testlib/neofs_testlib/cli/neogo/util.py b/neofs-testlib/neofs_testlib/cli/neogo/util.py new file mode 100644 index 000000000..87413b10e --- /dev/null +++ b/neofs-testlib/neofs_testlib/cli/neogo/util.py @@ -0,0 +1,15 @@ +from neofs_testlib.cli.cli_command import CliCommand +from neofs_testlib.shell import CommandResult + + +class NeoGoUtil(CliCommand): + def convert(self, post_data: str) -> CommandResult: + """Application version. + + Returns: + Command's result. + """ + return self._execute( + "util convert", + **{param: param_value for param, param_value in locals().items() if param not in ["self"]}, + ) diff --git a/neofs-testlib/neofs_testlib/env/env.py b/neofs-testlib/neofs_testlib/env/env.py index 0abc9ca81..20dff181d 100644 --- a/neofs-testlib/neofs_testlib/env/env.py +++ b/neofs-testlib/neofs_testlib/env/env.py @@ -9,6 +9,7 @@ import stat import string import subprocess +import tarfile import threading import time from collections import namedtuple @@ -22,11 +23,10 @@ import jinja2 import requests import yaml -from tenacity import retry, stop_after_attempt, wait_fixed - -from neofs_testlib.cli import NeofsAdm, NeofsCli, NeofsLens +from neofs_testlib.cli import NeofsAdm, NeofsCli, NeofsLens, NeoGo from neofs_testlib.shell import LocalShell from neofs_testlib.utils import wallet as wallet_utils +from tenacity import retry, stop_after_attempt, wait_fixed logger = logging.getLogger("neofs.testlib.env") @@ -64,11 +64,14 @@ def __init__(self, neofs_env_config: dict = None): self.neofs_s3_gw_path = os.getenv("NEOFS_S3_GW_BIN", "./neofs-s3-gw") self.neofs_rest_gw_path = os.getenv("NEOFS_REST_GW_BIN", "./neofs-rest-gw") self.alphabet_wallets_dir = NeoFSEnv._generate_temp_dir(prefix="ir_alphabet") + self.neofs_contract_dir = os.getenv("NEOFS_CONTRACT_DIR", "./neofs-contract") + self._init_default_wallet() # nodes inside env self.storage_nodes = [] self.inner_ring_nodes = [] self.s3_gw = None self.rest_gw = None + self.main_chain = None @property def morph_rpc(self): @@ -104,13 +107,21 @@ def neofs_lens( ) -> NeofsLens: return NeofsLens(self.shell, self.neofs_lens_path) + def neo_go(self) -> NeoGo: + return NeoGo(self.shell, self.neo_go_path) + def generate_cli_config(self, wallet: NodeWallet): cli_config_path = NeoFSEnv._generate_temp_file(extension="yml", prefix="cli_config") NeoFSEnv.generate_config_file(config_template="cli_cfg.yaml", config_path=cli_config_path, wallet=wallet) return cli_config_path + def generate_neo_go_config(self, wallet: NodeWallet): + neo_go_config_path = NeoFSEnv._generate_temp_file(extension="yml", prefix="neo_go_config") + NeoFSEnv.generate_config_file(config_template="neo_go_cfg.yaml", config_path=neo_go_config_path, wallet=wallet) + return neo_go_config_path + @allure.step("Deploy inner ring nodes") - def deploy_inner_ring_nodes(self, count=1): + def deploy_inner_ring_nodes(self, count=1, with_main_chain=False): for _ in range(count): new_inner_ring_node = InnerRing(self) new_inner_ring_node.generate_network_config() @@ -122,7 +133,15 @@ def deploy_inner_ring_nodes(self, count=1): ir_node.alphabet_wallet = alphabet_wallets.pop() for ir_node in self.inner_ring_nodes: - ir_node.start(wait_until_ready=False) + ir_node.generate_cli_config() + + if with_main_chain: + self.main_chain = MainChain(self) + self.main_chain.start() + self.deploy_neofs_contract() + + for ir_node in self.inner_ring_nodes: + ir_node.start(wait_until_ready=False, with_main_chain=with_main_chain) with allure.step("Wait until all IR nodes are READY"): for ir_node in self.inner_ring_nodes: @@ -178,6 +197,44 @@ def deploy_rest_gw(self): self.rest_gw.start() allure.attach(str(self.rest_gw), "rest_gw", allure.attachment_type.TEXT, ".txt") + @allure.step("Deploy neofs contract") + def deploy_neofs_contract(self): + if len(self.inner_ring_nodes) < 1: + raise RuntimeError( + "There should be at least a single IR instance configured(not started) to deploy neofs contract" + ) + neo_go = self.neo_go() + neo_go.nep17.transfer( + "GAS", + self.default_wallet.address, + f"http://{self.main_chain.rpc_address}", + from_address=self.inner_ring_nodes[-1].alphabet_wallet.address, + amount=9000, + force=True, + wallet_config=self.main_chain.neo_go_config, + ) + ir_alphabet_pubkey_from_neogo = wallet_utils.get_last_public_key_from_wallet_with_neogo( + self.neo_go(), self.default_wallet.path + ) + time.sleep(10) + result = neo_go.contract.deploy( + input_file=f"{self.neofs_contract_dir}/neofs/neofs_contract.nef", + manifest=f"{self.neofs_contract_dir}/neofs/config.json", + force=True, + rpc_endpoint=f"http://{self.main_chain.rpc_address}", + post_data=f"[ true ffffffffffffffffffffffffffffffffffffffff [ {ir_alphabet_pubkey_from_neogo} ] [ InnerRingCandidateFee 10 WithdrawFee 10 ] ]", + wallet_config=self.default_wallet_neogo_config, + ) + contract_hash = result.stdout.split("Contract: ")[-1].strip() + self.main_chain.neofs_contract_hash = contract_hash + assert self.main_chain.neofs_contract_hash, "Couldn't calculate neofs contract hash" + result = neo_go.util.convert(contract_hash).stdout + for line in result.splitlines(): + if "LE ScriptHash to Address" in line: + self.main_chain.neofs_contract_address = line.split("LE ScriptHash to Address")[-1].strip() + break + assert self.main_chain.neofs_contract_address, "Couldn't calculate neofs contract address" + @allure.step("Generate storage wallet") def generate_storage_wallet( self, @@ -251,6 +308,8 @@ def kill(self): self.rest_gw.process.kill() if self.s3_gw: self.s3_gw.process.kill() + if self.main_chain: + self.main_chain.process.kill() for sn in self.storage_nodes: sn.process.kill() for ir in self.inner_ring_nodes: @@ -267,6 +326,8 @@ def log_env_details_to_file(self): with open("env_details", "w") as fp: env_details = "" + env_details += f"{self.main_chain}\n" + for ir_node in self.inner_ring_nodes: env_details += f"{ir_node}\n" @@ -292,7 +353,7 @@ def log_versions_to_allure(self): @allure.step("Download binaries") def download_binaries(self): - logger.info("Going to download missing binaries, if any") + logger.info("Going to download missing binaries and contracts, if needed") deploy_threads = [] binaries = [ @@ -305,11 +366,12 @@ def download_binaries(self): (self.neofs_s3_authmate_path, "neofs_s3_authmate"), (self.neofs_s3_gw_path, "neofs_s3_gw"), (self.neofs_rest_gw_path, "neofs_rest_gw"), + (self.neofs_contract_dir, "neofs_contract"), ] for binary in binaries: binary_path, binary_name = binary - if not os.path.isfile(binary_path): + if not os.path.isfile(binary_path) and not os.path.isdir(binary_path): neofs_binary_params = self.neofs_env_config["binaries"][binary_name] allure_step_name = "Downloading " allure_step_name += f" {neofs_binary_params['repo']}/" @@ -337,6 +399,19 @@ def download_binaries(self): for t in deploy_threads: t.join() + def _init_default_wallet(self): + self.default_wallet = NodeWallet( + path=NeoFSEnv._generate_temp_file(prefix="default_neofs_env_wallet"), + address="", + password=self.default_password, + ) + wallet_utils.init_wallet(self.default_wallet.path, self.default_wallet.password) + self.default_wallet.address = wallet_utils.get_last_address_from_wallet( + self.default_wallet.path, self.default_wallet.password + ) + self.default_wallet_config = self.generate_cli_config(self.default_wallet) + self.default_wallet_neogo_config = self.generate_neo_go_config(self.default_wallet) + @classmethod def load(cls, persisted_path: str) -> "NeoFSEnv": with open(persisted_path, "rb") as fp: @@ -344,14 +419,14 @@ def load(cls, persisted_path: str) -> "NeoFSEnv": @classmethod @allure.step("Deploy simple neofs env") - def simple(cls, neofs_env_config: dict = None) -> "NeoFSEnv": + def simple(cls, neofs_env_config: dict = None, with_main_chain=False) -> "NeoFSEnv": if not neofs_env_config: neofs_env_config = yaml.safe_load( files("neofs_testlib.env.templates").joinpath("neofs_env_config.yaml").read_text() ) neofs_env = NeoFSEnv(neofs_env_config=neofs_env_config) neofs_env.download_binaries() - neofs_env.deploy_inner_ring_nodes() + neofs_env.deploy_inner_ring_nodes(with_main_chain=with_main_chain) neofs_env.deploy_storage_nodes( count=4, node_attrs={ @@ -361,11 +436,26 @@ def simple(cls, neofs_env_config: dict = None) -> "NeoFSEnv": 3: ["UN-LOCODE:FI HEL", "Price:44"], }, ) - neofs_env.neofs_adm().morph.set_config( - rpc_endpoint=f"http://{neofs_env.morph_rpc}", - alphabet_wallets=neofs_env.alphabet_wallets_dir, - post_data="ContainerFee=0 ContainerAliasFee=0 MaxObjectSize=524288", - ) + if with_main_chain: + neofs_adm = neofs_env.neofs_adm() + for sn in neofs_env.storage_nodes: + neofs_adm.morph.refill_gas( + rpc_endpoint=f"http://{neofs_env.morph_rpc}", + alphabet_wallets=neofs_env.alphabet_wallets_dir, + storage_wallet=sn.wallet.path, + gas="10.0", + ) + neofs_env.neofs_adm().morph.set_config( + rpc_endpoint=f"http://{neofs_env.morph_rpc}", + alphabet_wallets=neofs_env.alphabet_wallets_dir, + post_data="WithdrawFee=5", + ) + else: + neofs_env.neofs_adm().morph.set_config( + rpc_endpoint=f"http://{neofs_env.morph_rpc}", + alphabet_wallets=neofs_env.alphabet_wallets_dir, + post_data="ContainerFee=0 ContainerAliasFee=0 MaxObjectSize=524288", + ) time.sleep(30) neofs_env.deploy_s3_gw() neofs_env.deploy_rest_gw() @@ -412,9 +502,18 @@ def download_binary(repo: str, version: str, file: str, target: str): ) with open(target, mode="wb") as binary_file: binary_file.write(resp.content) - # make binary executable - current_perm = os.stat(target) - os.chmod(target, current_perm.st_mode | stat.S_IEXEC) + if "contract" in file: + # unpack contracts file into dir + tar_file = tarfile.open(target) + tar_file.extractall() + tar_file.close() + os.remove(target) + logger.info(f"rename: {file.rstrip(".tar.gz")} into {target}") + os.rename(file.rstrip(".tar.gz"), target) + else: + # make binary executable + current_perm = os.stat(target) + os.chmod(target, current_perm.st_mode | stat.S_IEXEC) @classmethod def _generate_temp_file(cls, extension: str = "", prefix: str = "tmp_file") -> str: @@ -433,6 +532,96 @@ def _generate_temp_dir(cls, prefix: str = "tmp_dir") -> str: return dir_path +class MainChain: + def __init__(self, neofs_env: NeoFSEnv): + self.neofs_env = neofs_env + self.cli_config = NeoFSEnv._generate_temp_file(extension="yml", prefix="main_chain_cli_config") + self.neo_go_config = NeoFSEnv._generate_temp_file(extension="yml", prefix="main_chain_neo_go_config") + self.main_chain_config_path = NeoFSEnv._generate_temp_file(extension="yml", prefix="main_chain_config") + self.main_chain_boltdb = NeoFSEnv._generate_temp_file(extension="db", prefix="main_chain_bolt_db") + self.rpc_address = f"{self.neofs_env.domain}:{NeoFSEnv.get_available_port()}" + self.p2p_address = f"{self.neofs_env.domain}:{NeoFSEnv.get_available_port()}" + self.stdout = "Not initialized" + self.stderr = "Not initialized" + self.process = None + self.neofs_contract_address = None + self.neofs_contract_hash = None + + def __str__(self): + return f""" + Main Chain: + - Config path: {self.main_chain_config_path} + - BoltDB path: {self.main_chain_boltdb} + - RPC address: {self.rpc_address} + - P2P address: {self.p2p_address} + - STDOUT: {self.stdout} + - STDERR: {self.stderr} + """ + + def __getstate__(self): + attributes = self.__dict__.copy() + del attributes["process"] + return attributes + + @allure.step("Start Main Chain") + def start(self, wait_until_ready=True): + if self.process is not None: + raise RuntimeError("This main chain instance has already been started") + + alphabet_wallet = self.neofs_env.inner_ring_nodes[-1].alphabet_wallet + + ir_alphabet_pubkey_from_neogo = wallet_utils.get_last_public_key_from_wallet_with_neogo( + self.neofs_env.neo_go(), alphabet_wallet.path + ) + + logger.info(f"Generating main chain config at: {self.main_chain_config_path}") + main_chain_config_template = "main_chain.yaml" + + NeoFSEnv.generate_config_file( + config_template=main_chain_config_template, + config_path=self.main_chain_config_path, + custom=Path(main_chain_config_template).is_file(), + wallet=alphabet_wallet, + public_key=ir_alphabet_pubkey_from_neogo, + main_chain_boltdb=self.main_chain_boltdb, + p2p_address=self.p2p_address, + rpc_address=self.rpc_address, + sn_addresses=[sn.endpoint for sn in self.neofs_env.storage_nodes], + ) + logger.info(f"Generating CLI config at: {self.cli_config}") + NeoFSEnv.generate_config_file( + config_template="cli_cfg.yaml", config_path=self.cli_config, wallet=alphabet_wallet + ) + logger.info(f"Generating NEO GO config at: {self.neo_go_config}") + NeoFSEnv.generate_config_file( + config_template="neo_go_cfg.yaml", config_path=self.neo_go_config, wallet=alphabet_wallet + ) + logger.info(f"Launching Main Chain:{self}") + self._launch_process() + logger.info(f"Launched Main Chain:{self}") + if wait_until_ready: + logger.info("Wait until Main Chain is READY") + self._wait_until_ready() + + def _launch_process(self): + self.stdout = NeoFSEnv._generate_temp_file(prefix="main_chain_stdout") + self.stderr = NeoFSEnv._generate_temp_file(prefix="main_chain_stderr") + stdout_fp = open(self.stdout, "w") + stderr_fp = open(self.stderr, "w") + self.process = subprocess.Popen( + [self.neofs_env.neo_go_path, "node", "--config-file", self.main_chain_config_path, "--debug"], + stdout=stdout_fp, + stderr=stderr_fp, + ) + + @retry(wait=wait_fixed(10), stop=stop_after_attempt(50), reraise=True) + def _wait_until_ready(self): + result = self.neofs_env.neo_go().query.height(rpc_endpoint=f"http://{self.rpc_address}") + logger.info("WAIT UNTIL MAIN CHAIN IS READY:") + logger.info(result.stdout) + logger.info(result.stderr) + + class InnerRing: def __init__(self, neofs_env: NeoFSEnv): self.neofs_env = neofs_env @@ -481,8 +670,14 @@ def generate_network_config(self): default_password=self.neofs_env.default_password, ) + def generate_cli_config(self): + logger.info(f"Generating CLI config at: {self.cli_config}") + NeoFSEnv.generate_config_file( + config_template="cli_cfg.yaml", config_path=self.cli_config, wallet=self.alphabet_wallet + ) + @allure.step("Start Inner Ring node") - def start(self, wait_until_ready=True): + def start(self, wait_until_ready=True, with_main_chain=False): if self.process is not None: raise RuntimeError("This inner ring node instance has already been started") logger.info(f"Generating IR config at: {self.ir_node_config_path}") @@ -514,10 +709,9 @@ def start(self, wait_until_ready=True): control_public_key=wallet_utils.get_last_public_key_from_wallet( self.alphabet_wallet.path, self.alphabet_wallet.password ), - ) - logger.info(f"Generating CLI config at: {self.cli_config}") - NeoFSEnv.generate_config_file( - config_template="cli_cfg.yaml", config_path=self.cli_config, wallet=self.alphabet_wallet + without_mainnet=f"{not with_main_chain}".lower(), + main_chain_rpc="localhost:1234" if not with_main_chain else self.neofs_env.main_chain.rpc_address, + neofs_contract_hash="123" if not with_main_chain else self.neofs_env.main_chain.neofs_contract_hash, ) logger.info(f"Launching Inner Ring Node:{self}") self._launch_process() diff --git a/neofs-testlib/neofs_testlib/env/templates/ir.yaml b/neofs-testlib/neofs_testlib/env/templates/ir.yaml index 88a73ed8b..24905295f 100644 --- a/neofs-testlib/neofs_testlib/env/templates/ir.yaml +++ b/neofs-testlib/neofs_testlib/env/templates/ir.yaml @@ -8,9 +8,22 @@ wallet: address: {{ wallet.address }} # Account address in the wallet; ignore to use default address password: {{ wallet.password }} # Account password in the wallet -without_mainnet: true # Run application in single chain environment without mainchain +# Toggling the sidechain-only mode +without_mainnet: {{ without_mainnet }} + +# Neo main chain RPC settings +mainnet: + endpoints: # List of websocket RPC endpoints in mainchain; ignore if mainchain is disabled + - ws://{{ main_chain_rpc }}/ws + dial_timeout: 5s # Timeout for RPC client connection to mainchain; ignore if mainchain is disabled + reconnections_number: 5 # number of reconnection attempts + reconnections_delay: 5s # time delay b/w reconnection attempts morph: + validators: # List of hex-encoded 33-byte public keys of sidechain validators to vote for at application startup + {%- for public_key in public_keys %} + - {{ public_key }} # Hex-encoded public key + {%- endfor %} dial_timeout: 5s # Timeout for RPC client connection to sidechain reconnections_number: 5 # number of reconnection attempts reconnections_delay: 5s # time delay b/w reconnection attempts @@ -51,11 +64,6 @@ fschain_autodeploy: true nns: system_email: usr@domain.io -mainnet: - dial_timeout: 5s # Timeout for RPC client connection to mainchain; ignore if mainchain is disabled - reconnections_number: 5 # number of reconnection attempts - reconnections_delay: 5s # time delay b/w reconnection attempts - control: authorized_keys: # List of hex-encoded 33-byte public keys that have rights to use the control service - {{ control_public_key }} @@ -127,3 +135,6 @@ netmap_cleaner: settlement: basic_income_rate: 0 # Optional: override basic income rate value from network config; applied only in debug mode audit_fee: 0 # Optional: override audit fee value from network config; applied only in debug mode + +contracts: + neofs: {{ neofs_contract_hash }} # Address of NeoFS contract in mainchain; ignore if mainchain is disabled diff --git a/neofs-testlib/neofs_testlib/env/templates/main_chain.yaml b/neofs-testlib/neofs_testlib/env/templates/main_chain.yaml new file mode 100644 index 000000000..8e7aee2f0 --- /dev/null +++ b/neofs-testlib/neofs_testlib/env/templates/main_chain.yaml @@ -0,0 +1,59 @@ +ProtocolConfiguration: + Magic: 56753 + MaxTraceableBlocks: 200000 + TimePerBlock: 1s + MemPoolSize: 50000 + StandbyCommittee: + - {{ public_key }} + ValidatorsCount: 1 + VerifyTransactions: true + P2PSigExtensions: false + +ApplicationConfiguration: + SkipBlockVerification: false + DBConfiguration: + Type: "boltdb" + BoltDBOptions: + FilePath: "{{ main_chain_boltdb }}" + P2P: + Addresses: + - "{{ p2p_address }}" + DialTimeout: 3s + ProtoTickInterval: 2s + PingInterval: 30s + PingTimeout: 90s + MaxPeers: 10 + AttemptConnPeers: 5 + MinPeers: 0 + Relay: true + RPC: + Addresses: + - "{{ rpc_address }}" + Enabled: true + SessionEnabled: true + EnableCORSWorkaround: false + MaxGasInvoke: 1500000000 + Prometheus: + Enabled: false + Pprof: + Enabled: false + Consensus: + Enabled: true + UnlockWallet: + Path: "{{ wallet.path }}" + Password: "{{ wallet.password }}" + Oracle: + Enabled: true + NeoFS: + Nodes: + {%- for sn_address in sn_addresses %} + - {{ sn_address }} + {%- endfor %} + UnlockWallet: + Path: "{{ wallet.path }}" + Password: "{{ wallet.password }}" + P2PNotary: + Enabled: false + UnlockWallet: + Path: "{{ wallet.path }}" + Password: "{{ wallet.password }}" diff --git a/neofs-testlib/neofs_testlib/env/templates/neo_go_cfg.yaml b/neofs-testlib/neofs_testlib/env/templates/neo_go_cfg.yaml new file mode 100644 index 000000000..a5b94567e --- /dev/null +++ b/neofs-testlib/neofs_testlib/env/templates/neo_go_cfg.yaml @@ -0,0 +1,2 @@ +Path: {{ wallet.path }} +Password: {{ wallet.password }} diff --git a/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml b/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml index 46fe31cfe..adee934f8 100644 --- a/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml +++ b/neofs-testlib/neofs_testlib/env/templates/neofs_env_config.yaml @@ -43,3 +43,8 @@ binaries: repo: 'nspcc-dev/neo-go' version: 'v0.106.0' file: 'neo-go-linux-amd64' + + neofs_contract: + repo: 'nspcc-dev/neofs-contract' + version: 'v0.19.1' + file: 'neofs-contract-v0.19.1.tar.gz' diff --git a/neofs-testlib/neofs_testlib/env/templates/network.yaml b/neofs-testlib/neofs_testlib/env/templates/network.yaml index b213bbefe..66adbf0f4 100644 --- a/neofs-testlib/neofs_testlib/env/templates/network.yaml +++ b/neofs-testlib/neofs_testlib/env/templates/network.yaml @@ -29,3 +29,4 @@ storage: http: {{ default_password }} rest: {{ default_password }} xk6: {{ default_password }} + main_chain: {{ default_password }} diff --git a/neofs-testlib/neofs_testlib/utils/wallet.py b/neofs-testlib/neofs_testlib/utils/wallet.py index 7d3488c3e..d206501b6 100644 --- a/neofs-testlib/neofs_testlib/utils/wallet.py +++ b/neofs-testlib/neofs_testlib/utils/wallet.py @@ -3,6 +3,7 @@ from neo3.wallet import account as neo3_account from neo3.wallet import wallet as neo3_wallet +from neofs_testlib.cli.neogo import NeoGo logger = logging.getLogger("neofs.testlib.utils") @@ -71,3 +72,9 @@ def get_last_public_key_from_wallet( public_key = wallet.accounts[-1].public_key logger.info(f"got public_key: {public_key}") return public_key + + +def get_last_public_key_from_wallet_with_neogo(neo_go: NeoGo, wallet_path: str) -> str: + public_key = neo_go.wallet.dump_keys(wallet=wallet_path).stdout.split(":")[-1].strip() + logger.info(f"got public_key: {public_key}") + return public_key diff --git a/pytest_tests/tests/payment/test_deposit_withdrawal.py b/pytest_tests/tests/payment/test_deposit_withdrawal.py new file mode 100644 index 000000000..605e496a0 --- /dev/null +++ b/pytest_tests/tests/payment/test_deposit_withdrawal.py @@ -0,0 +1,103 @@ +import time + +import allure +import pytest +from neofs_testlib.cli import NeofsCli, NeoGo +from neofs_testlib.env.env import NeoFSEnv, NodeWallet +from neofs_testlib.utils import wallet as wallet_utils + + +@pytest.fixture +def neofs_env_with_mainchain(): + neofs_env = NeoFSEnv.simple(with_main_chain=True) + yield neofs_env + neofs_env.kill() + + +def get_wallet_balance(neofs_env: NeoFSEnv, neo_go: NeoGo, wallet: NodeWallet, wallet_config: str) -> float: + result = neo_go.nep17.balance( + wallet.address, "GAS", f"http://{neofs_env.main_chain.rpc_address}", wallet_config=wallet_config + ) + balance = 0.0 + for line in result.stdout.splitlines(): + if "Amount" in line: + balance = float(line.split("Amount :")[-1].strip()) + return balance + + +def get_neofs_balance(neofs_env: NeoFSEnv, neofs_cli: NeofsCli, wallet: NodeWallet) -> float: + return float( + neofs_cli.accounting.balance( + wallet=wallet.path, + rpc_endpoint=neofs_env.sn_rpc, + address=wallet_utils.get_last_address_from_wallet(wallet.path, wallet.password), + ).stdout.strip() + ) + + +class TestDepositWithdrawal: + def test_deposit_withdrawal(self, neofs_env_with_mainchain: NeoFSEnv): + neofs_env = neofs_env_with_mainchain + + with allure.step("Create wallet for deposits/withdrawals"): + wallet = NodeWallet( + path=NeoFSEnv._generate_temp_file(prefix="deposit_withdrawal_test_wallet"), + address="", + password=neofs_env.default_password, + ) + wallet_utils.init_wallet(wallet.path, wallet.password) + wallet.address = wallet_utils.get_last_address_from_wallet(wallet.path, wallet.password) + neo_go_wallet_config = neofs_env.generate_neo_go_config(wallet) + cli_wallet_config = neofs_env.generate_cli_config(wallet) + + with allure.step("Transfer some money to created wallet"): + neo_go = neofs_env.neo_go() + neo_go.nep17.transfer( + "GAS", + wallet.address, + f"http://{neofs_env.main_chain.rpc_address}", + from_address=neofs_env.inner_ring_nodes[-1].alphabet_wallet.address, + amount=1000, + force=True, + wallet_config=neofs_env.main_chain.neo_go_config, + ) + time.sleep(10) + assert ( + get_wallet_balance(neofs_env, neo_go, wallet, neo_go_wallet_config) == 1000.0 + ), "Money transfer from alphabet to test wallet didn't succeed" + + with allure.step("Deposit money to neofs contract"): + neo_go.nep17.transfer( + "GAS", + neofs_env.main_chain.neofs_contract_address, + f"http://{neofs_env.main_chain.rpc_address}", + from_address=wallet.address, + amount=100, + force=True, + wallet_config=neo_go_wallet_config, + ) + time.sleep(10) + assert ( + get_wallet_balance(neofs_env, neo_go, wallet, neo_go_wallet_config) <= 900 + ), "Wallet balance is not correct after deposit" + neofs_cli = neofs_env.neofs_cli(cli_wallet_config) + assert ( + get_neofs_balance(neofs_env, neofs_cli, wallet) == 100 + ), "Wallet balance in neofs is not correct after deposit" + + with allure.step("Withdraw some money back to the wallet"): + neo_go.contract.invokefunction( + neofs_env.main_chain.neofs_contract_hash, + rpc_endpoint=f"http://{neofs_env.main_chain.rpc_address}", + wallet_config=neo_go_wallet_config, + method="withdraw", + arguments=f"{wallet.address} 50", + multisig_hash=f"{wallet.address}:Global", + force=True, + ) + assert ( + get_neofs_balance(neofs_env, neofs_cli, wallet) == 50 + ), "Wallet balance in neofs is not correct after withdrawal" + assert ( + get_wallet_balance(neofs_env, neo_go, wallet, neo_go_wallet_config) > 940 + ), "Wallet balance is not correct after withdrawal"