def test_two_network_repeated_deploy(two_node_network): """ Test that a repeated deploy is rejected on a different node """ nodes = two_node_network.docker_nodes clis = [CLI(node) for node in nodes] accounts = (Account("genesis"), Account(1)) # Generate and save signed_deploy with signed_deploy_file_path(clis[0], accounts[0]) as signed_deploy_path: # First deployment of signed_deploy from node-0 should succeed deploy_hash = clis[0]("send-deploy", "-i", signed_deploy_path) block_hash = nodes[0].wait_for_deploy_processed_and_get_block_hash(deploy_hash) deploy_info = clis[0]("show-deploy", deploy_hash) assert not deploy_info.processing_results[0].is_error wait_for_block_hash_propagated_to_all_nodes(nodes, block_hash) # Second deployment of signed_deploy to node-1 should fail. deploy_hash = clis[1]("send-deploy", "-i", signed_deploy_path) deploy_info = clis[1]("show-deploy", deploy_hash) assert not deploy_info.processing_results[0].is_error result = nodes[1].p_client.client.wait_for_deploy_processed(deploy_hash) assert "DISCARDED" in str(result) assert "Duplicate or expired" in str(result)
def create_associate_deploy(acct_num: int): """ Add associated key of acct_num + 1 to acct_num account """ acct = Account(acct_num) associated_acct = Account(acct_num + 1) args = ABI.args([ ABI.account("account", associated_acct.public_key_binary), ABI.u32("amount", 1), ]) return node.p_client.deploy( from_address=acct.public_key_hex, session_contract=Contract.ADD_ASSOCIATED_KEY, public_key=acct.public_key_path, private_key=acct.private_key_path, session_args=args, )
def test_unbonding_without_bonding(one_node_network_fn): """ Feature file: consensus.feature Scenario: unbonding a validator node which was not bonded to an existing network. """ bonding_amount = 1 account = Account(BONDING_ACCT) assert_pre_state_of_network(one_node_network_fn) add_funded_account_to_network(one_node_network_fn, BONDING_ACCT) assert ( len(one_node_network_fn.docker_nodes) == 2 ), "Total number of nodes should be 2." node0, node1 = one_node_network_fn.docker_nodes r = node0.d_client.unbond(bonding_amount, account.private_key_docker_path) assert "Success!" in r r = node0.d_client.propose() block_hash = extract_block_hash_from_propose_output(r) assert block_hash is not None # block_hash, account = unbond_from_network(one_node_network_fn, bonding_amount, BONDING_ACCT) r = node0.client.show_deploys(block_hash)[0] assert r.is_error is True assert r.error_message == "Exit code: 65280" block = node1.client.show_block(block_hash) block_ds = parse_show_block(block) bonds = list( filter( lambda x: x.validator_public_key == account.public_key_hex, block_ds.summary.header.state.bonds, ) ) assert len(bonds) == 0
def test_account(self, node, amount=TEST_ACCOUNT_INITIAL_BALANCE) -> Account: name = test_name() if not name: # This happens when a thread tries to deploy. # Name of the test that spawned the thread does not appear on the inspect.stack. # Threads that don't want to use genesis account # should pass from_address, public_key and private_key to deploy explicitly. return self.genesis_account elif name not in self.test_accounts: with self._accounts_lock: self.test_accounts[name] = Account(self.next_key) logging.info( f"=== Creating test account #{self.next_key} {self.test_accounts[name].public_key_hex} for {name} " ) block_hash = node.transfer_to_account(self.next_key, amount) # Waiting for the block with transaction that created new account to propagate to all nodes. # Expensive, but some tests may rely on it. wait_for_block_hash_propagated_to_all_nodes( node.cl_network.docker_nodes, block_hash) for deploy in node.client.show_deploys(block_hash): assert (deploy.is_error is False), f"Account creation failed: {deploy}" self.next_key += 1 return self.test_accounts[name]
def test_scala_client_balance(one_node_network): node: DockerNode = one_node_network.docker_nodes[0] accounts = [Account(i) for i in range(1, 3)] block_hash = list(node.p_client.show_blocks(1))[0].summary.block_hash.hex() initial_bal = { account.file_id: balance(node, account.public_key_hex, block_hash) for account in accounts } transfer_amt = {1: 100, 2: 800} # All have to come from genesis to have enough to pay for transaction hashes = node.transfer_to_accounts([(1, transfer_amt[1]), (2, transfer_amt[2])]) current_bal = { account.file_id: balance(node, account.public_key_hex, hashes[-1]) for account in accounts } for file_id in (1, 2): assert current_bal[ file_id] == initial_bal[file_id] + transfer_amt[file_id]
def test_one_network_repeated_deploy(one_node_network_fn): """ Test that a repeated deploy is rejected on same node. """ node = one_node_network_fn.docker_nodes[0] cli = CLI(node) account = Account("genesis") # Generate and save signed_deploy with signed_deploy_file_path(cli, account) as signed_deploy_path: # First deployment of signed_deploy, should succeed deploy_hash = cli("send-deploy", "-i", signed_deploy_path) deploy_info = cli.node.p_client.client.wait_for_deploy_processed(deploy_hash) blocks_with_deploy = [ bi.block_info.summary.block_hash.hex() for bi in deploy_info.processing_results ] blocks_with_deploy_after_replay = [ bi.block_info.summary.block_hash.hex() for bi in deploy_info.processing_results ] assert not deploy_info.processing_results[0].is_error # Second deployment of signed_deploy should fail deploy_hash = cli("send-deploy", "-i", signed_deploy_path) deploy_info = cli.node.p_client.client.wait_for_deploy_processed(deploy_hash) assert not deploy_info.processing_results[0].is_error blocks_with_deploy_after_replay = [ bi.block_info.summary.block_hash.hex() for bi in deploy_info.processing_results ] assert blocks_with_deploy_after_replay == blocks_with_deploy
def unbond_from_network( network: OneNodeNetwork, bonding_amount: int, account_number: int ): node = network.docker_nodes[1] account = Account(account_number) r = node.d_client.unbond(bonding_amount, account.private_key_docker_path) assert "Success!" in r r = node.d_client.propose() block_hash = extract_block_hash_from_propose_output(r) assert block_hash is not None return block_hash, account
def bond( self, session_contract: str, amount: int, from_account_id: Union[str, int] = "genesis", ) -> str: # NOTE: The Scala client is bundled with a bond contract that expects long_value, # but the integration test version expects int. json_args = json.dumps([{"name": "amount", "value": {"int_value": amount}}]) return self._deploy_and_propose_with_abi_args( session_contract, Account(from_account_id), json_args )
def unbond( self, session_contract: str, maybe_amount: Optional[int] = None, from_account_id: Union[str, int] = "genesis", ) -> str: json_args = json.dumps( [{"name": "amount", "value": {"int_value": maybe_amount or 0}}] ) return self._deploy_with_abi_args_and_get_block_hash( session_contract, Account(from_account_id), json_args )
def bond_to_the_network(network: OneNodeNetwork, bond_amount: int, account_number: int): # Using account that will not exist in bonds.txt from high number account = Account(account_number) node0, node1 = network.docker_nodes response = node0.d_client.bond( amount=bond_amount, private_key=account.private_key_docker_path ) assert "Success!" in response response = node0.d_client.propose() block_hash = extract_block_hash_from_propose_output(response) assert block_hash is not None return block_hash, account
def test_transfer_to_accounts(node): # Notated uses of account ids in common.py a_id = 300 b_id = 299 c_id = 298 initial_amt = 100000000 acct_a = Account(a_id) acct_b = Account(b_id) acct_c = Account(c_id) # Setup accounts with enough to transfer and pay for transfer node.transfer_to_accounts([(a_id, initial_amt), (b_id, initial_amt)]) with raises(Exception): # Acct a has not enough funds so it should fail node.transfer_to_account(to_account_id=c_id, amount=initial_amt * 10, from_account_id=a_id) # This is throwing an Exit 1. (Transfer Failure in Contract) node.transfer_to_account(to_account_id=c_id, amount=700, from_account_id=b_id) blocks = node.p_client.show_blocks(10) block = blocks.__next__() block_hash = block.summary.block_hash.hex() acct_a_bal = node.d_client.get_balance(acct_a.public_key_hex, block_hash) assert (acct_a_bal < initial_amt ), "Should not have transferred any money, but spent on payment" acct_b_bal = node.d_client.get_balance(acct_b.public_key_hex, block_hash) assert (acct_b_bal < initial_amt - 700), "Should be transfer_amt - 700 - payment for transfer" acct_c_bal = node.d_client.get_balance(acct_c.public_key_hex, block_hash) assert acct_c_bal == 700, "Should be result of only transfers in"
def test_transfer_with_overdraft(node): # Notated uses of account ids in common.py a_id = 297 b_id = 296 acct_a = Account(a_id) acct_b = Account(b_id) initial_amt = 100000000 block_hash = node.transfer_to_account(to_account_id=a_id, amount=initial_amt) deploys = node.client.show_deploys(block_hash) assert not deploys[0].is_error, f"error_message: {deploys[0].error_message}" # Response not used, but assures account exist _ = account_state(node, block_hash, acct_a) # Should error as account doesn't exist. with raises(Exception): _ = account_state(block_hash, acct_b.public_key_hex) # No API currently exists for getting balance to check transfer. # Transfer 750000 from acct1... to acct2... block_hash = node.transfer_to_account(to_account_id=b_id, amount=750, from_account_id=a_id) deploys = node.client.show_deploys(block_hash) assert not deploys[0].is_error, f"error_message: {deploys[0].error_message}" # Response not used, but assures account exist _ = account_state(node, block_hash, acct_b) # Should fail with acct_a overdrawn. Requires assert in contract to generate is_error. with raises(Exception): _ = node.transfer_to_account(to_account_id=b_id, amount=initial_amt * 10, from_account_id=a_id)
def bond_to_the_network(network: OneNodeNetwork, bond_amount: int, account_number: int, cli_method): account = Account(account_number) node0, node1 = network.docker_nodes cli = cli_method(node0) # fmt: off deploy_hash = cli("bond", "--amount", bond_amount, '--private-key', cli.private_key_path(account), '--payment-amount', EXECUTION_PAYMENT_AMOUNT) # fmt: on block_hash = cli.node.wait_for_deploy_processed_and_get_block_hash( deploy_hash, on_error_raise=False) return block_hash, account
def unbond_from_network(network: OneNodeNetwork, bonding_amount: int, account_number: int, cli_method): node = network.docker_nodes[1] account = Account(account_number) cli = cli_method(node) # fmt: off deploy_hash = cli("unbond", "--amount", bonding_amount, "--private-key", cli.private_key_path(account), "--payment-amount", EXECUTION_PAYMENT_AMOUNT) # fmt: on block_hash = cli.node.wait_for_deploy_processed_and_get_block_hash( deploy_hash, on_error_raise=False) return block_hash, account
def test_error_in_payment_contract(payment_node_network): network = payment_node_network node0: DockerNode = network.docker_nodes[0] node0.use_docker_client() blocks = parse_show_blocks(node0.d_client.show_blocks(1000)) genesis_hash = blocks[0].summary.block_hash assert len( blocks) == 1 # There should be only one block - the genesis block genesis_balance = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=genesis_hash) assert genesis_balance == INITIAL_MOTES_AMOUNT from_account = Account("genesis") to_account = Account(1) session_args = ABI.args([ ABI.account("account", to_account.public_key_hex), ABI.u64("amount", 10**7) ]) payment_args = ABI.args([ABI.u512("amount", 10**6)]) node0.p_client.deploy( from_address=from_account.public_key_hex, session_contract=Contract.TRANSFER_TO_ACCOUNT, payment_contract=Contract.DIRECT_REVERT, public_key=from_account.public_key_path, private_key=from_account.private_key_path, gas_price=1, session_args=session_args, payment_args=payment_args, ) genesis_balance_after_transfer = node0.client.get_balance( account_address=GENESIS_ACCOUNT.public_key_hex, block_hash=parse_show_blocks( node0.d_client.show_blocks(1000))[0].summary.block_hash, ) assert genesis_balance == genesis_balance_after_transfer
def unbond_from_network(network: OneNodeNetwork, bonding_amount: int, account_number: int): node = network.docker_nodes[1] account = Account(account_number) cli = CLI(node) # fmt: off cli("unbond", "--amount", bonding_amount, '--public-key', account.public_key_path, "--private-key", account.private_key_path, "--payment-amount", EXECUTION_PAYMENT_AMOUNT, "--from", account.public_key_hex) # fmt: on block_hash = cli("propose") return block_hash, account
def bond_to_the_network(network: OneNodeNetwork, bond_amount: int, account_number: int): # Using account that will not exist in bonds.txt from high number account = Account(account_number) node0, node1 = network.docker_nodes cli = CLI(node0) # fmt: off cli("bond", "--amount", bond_amount, '--public-key', account.public_key_path, '--private-key', account.private_key_path, '--payment-amount', EXECUTION_PAYMENT_AMOUNT, '--from', account.public_key_hex) # fmt: on block_hash = cli("propose") return block_hash, account
def test_basic_transfer_to_account(payment_node_network): network = payment_node_network node = network.docker_nodes[0] to_account = Account(1) transfer_to_account( node, node.genesis_account.public_key_hex, to_account.public_key_hex, 1000000, public_key=node.genesis_account.public_key_path, private_key=node.genesis_account.private_key_path, )
def add_funded_account_to_network(network: OneNodeNetwork, account_number: int): node0 = network.docker_nodes[0] prev_number = len(network.docker_nodes) account = network.add_new_node_to_network(account=Account(account_number)) assert (len(network.docker_nodes) == prev_number + 1), f"Total number of nodes should be {prev_number + 1}." response = node0.d_client.transfer( amount=1000000000, private_key=GENESIS_ACCOUNT.private_key_docker_path, target_account=account.public_key, ) assert "Success!" in response deploy_hash = response.split()[2] node0.wait_for_deploy_processed_and_get_block_hash(deploy_hash)
def create_genesis_accounts_file(self) -> None: bond_amount = self.config.bond_amount N = self.NUMBER_OF_BONDS # Creating a file where the node is expecting to see overrides, i.e. at ~/.casperlabs/chainspec/genesis path = f"{self.host_chainspec_dir}/genesis/accounts.csv" os.makedirs(os.path.dirname(path)) with open(path, "a") as f: # Give the initial motes to the genesis account, so that tests which use # this way of creating accounts work. But the accounts could be just # created this way, without having to do a transfer. f.write(f"{GENESIS_ACCOUNT.public_key},{self.cl_network.initial_motes},0\n") for i, pair in enumerate( Account(i) for i in range(FIRST_VALIDATOR_ACCOUNT, FIRST_VALIDATOR_ACCOUNT + N) ): bond = bond_amount(i, N) f.write(f"{pair.public_key},0,{bond}\n")
def add_funded_account_to_network(network: OneNodeNetwork, account_number: int): node0 = network.docker_nodes[0] cli = CLI(node0) prev_number = len(network.docker_nodes) account = network.add_new_node_to_network(account=Account(account_number)) assert (len(network.docker_nodes) == prev_number + 1), f"Total number of nodes should be {prev_number + 1}." response = node0.d_client.transfer( amount=1000000000, private_key=GENESIS_ACCOUNT.private_key_docker_path, target_account=account.public_key, ) assert "Success!" in response block_hash = cli("propose") for deployInfo in node0.p_client.showDeploys(block_hash): assert (deployInfo.is_error is False), f"Transfer Failed: {deployInfo.error_message}"
def unbond( self, session_contract: str, maybe_amount: Optional[int] = None, from_account_id: Union[str, int] = "genesis", ) -> str: # NOTE: The Scala client is bundled with an unbond contract that expects an optional # value, but the integration tests have their own version which expects an int # and turns 0 into None inside the contract itself # amount = {} if maybe_amount is None else {"int_value": maybe_amount} # json_args = json.dumps( # [{"name": "amount", "value": {"optional_value": amount}}] # ) json_args = json.dumps( [{"name": "amount", "value": {"int_value": maybe_amount or 0}}] ) return self._deploy_and_propose_with_abi_args( session_contract, Account(from_account_id), json_args )
def add_funded_account_to_network(network: OneNodeNetwork, account_number: int): node0 = network.docker_nodes[0] prev_number = len(network.docker_nodes) account = network.add_new_node_to_network(account=Account(account_number)) assert ( len(network.docker_nodes) == prev_number + 1 ), f"Total number of nodes should be {prev_number + 1}." response = node0.d_client.transfer( amount=1000000000, private_key=GENESIS_ACCOUNT.private_key_docker_path, target_account=account.public_key, ) assert "Success!" in response response = node0.d_client.propose() block_hash = extract_block_hash_from_propose_output(response) assert block_hash is not None r = node0.client.show_deploys(block_hash)[0] assert r.is_error is False, f"Transfer Failed: {r.error_message}" assert r.error_message == ""
def create_genesis_accounts_file(self, path: str = None) -> None: bond_amount = self.config.bond_amount N = self.NUMBER_OF_BONDS try: os.makedirs(os.path.dirname(path)) except OSError: pass with open(path, "w") as f: # Give the initial motes to the genesis account, so that tests which use # this way of creating accounts work. But the accounts could be just # created this way, without having to do a transfer. f.write( f"{GENESIS_ACCOUNT.public_key},{self.cl_network.initial_motes},0\n" ) for i, pair in enumerate( Account(i) for i in range(FIRST_VALIDATOR_ACCOUNT, FIRST_VALIDATOR_ACCOUNT + N)): bond = bond_amount(i, N) f.write(f"{pair.public_key},0,{bond}\n")
def test_non_account_precondition_failure(trillion_payment_node_network): node = trillion_payment_node_network.docker_nodes[0] # Getting a non-existent account non_existent_account = Account(300) # Client returns deploy hash, but will not stay in buffer for proposes. _, deploy_hash = node.p_client.deploy( from_address=non_existent_account.public_key_hex, public_key=non_existent_account.public_key_path, private_key=non_existent_account.private_key_path, session_contract=Contract.HELLO_NAME_DEFINE, ) # Will have InternalError as no deploys to propose with pytest.raises(Exception) as e: _ = node.p_client.propose() # Verify reason for propose failure assert e.typename == "InternalError" assert str(e.value) == "StatusCode.OUT_OF_RANGE: No new deploys."
def test_non_account_precondition_failure(trillion_payment_node_network): node = trillion_payment_node_network.docker_nodes[0] # Getting a non-existent account non_existent_account = Account(300) # Client returns deploy hash, but will not stay in buffer for proposes. deploy_hash = node.p_client.deploy( from_address=non_existent_account.public_key_hex, public_key=non_existent_account.public_key_path, private_key=non_existent_account.private_key_path, session_contract=Contract.HELLO_NAME_DEFINE, ) # Will have InternalError as no deploys to propose with pytest.raises(Exception) as e: node.p_client.client.wait_for_deploy_processed(deploy_hash) # Verify reason for propose failure assert "DISCARDED" in str(e.value) assert "Authorization failure: not authorized." in str(e.value)
def test_unbonding_without_bonding(one_node_network_fn, acct_num, cli_method): """ Feature file: consensus.feature Scenario: unbonding a validator node which was not bonded to an existing network. """ onn = one_node_network_fn bonding_amount = 1 account = Account(acct_num) assert_pre_state_of_network(onn) add_funded_account_to_network(onn, acct_num) assert len(onn.docker_nodes) == 2, "Total number of nodes should be 2." node0, node1 = onn.docker_nodes cli = cli_method(node0) deploy_hash = cli( "unbond", "--amount", bonding_amount, "--private-key", cli.private_key_path(account), "--payment-amount", EXECUTION_PAYMENT_AMOUNT, ) block_hash = cli.node.wait_for_deploy_processed_and_get_block_hash( deploy_hash, on_error_raise=False) r = node0.client.show_deploys(block_hash)[0] assert r.is_error is True assert r.error_message == "PoS error: 0" block = node1.client.show_block(block_hash) block_ds = parse_show_block(block) bonds = list( filter( lambda x: x.validator_public_key == account.public_key_hex, block_ds.summary.header.state.bonds, )) assert len(bonds) == 0
async def test_transfer_and_balance(node, client): test_account = Account(1) block_infos = await client.show_blocks() block_hash = block_infos[0].summary.block_hash.hex() genesis_account_balance = await client.balance( GENESIS_ACCOUNT.public_key_hex, block_hash) assert genesis_account_balance > 0 transfer_amount = 10**7 deploy_hash = await client.transfer( test_account.public_key_hex, transfer_amount, from_addr=GENESIS_ACCOUNT.public_key_hex, public_key=GENESIS_ACCOUNT.public_key_path, private_key=GENESIS_ACCOUNT.private_key_path, payment_amount=PAYMENT_AMOUNT, ) deploy_info = await client.wait_for_deploy_processed(deploy_hash) block_hash = deploy_info.processing_results[ 0].block_info.summary.block_hash.hex() test_account_balance = await client.balance(test_account.public_key_hex, block_hash) assert test_account_balance == transfer_amount
import json import time from casperlabs_client import ABI from casperlabs_local_net.casperlabs_accounts import Account from casperlabs_local_net.casperlabs_network import TrillionPaymentNodeNetwork from casperlabs_local_net.common import Contract """ Accounts have two threshold values: key_management_threshold deploy_threshold Both are initialized at 1. """ IDENTITY_KEY = Account(1) # 9d39 DEPLOY_KEY = Account(2) # 4e74 DEPLOY_KEY_WEIGHT = 10 KEY_MGMT_KEY = Account(3) # 58f7 KEY_MGMT_KEY_WEIGHT = 20 HIGH_WEIGHT_KEY = Account(4) # 1ca8 HIGH_WEIGHT_KEY_WEIGHT = 200 INITIAL_ACCOUNT_VALUE = 1000000000 def _add_update_associate_key(node, weight_key: Account, key: Account, weight: int, contract: str): """ Handles both add and update calls due to commonality """ session_args = ABI.args([ ABI.account("account", key.public_key_hex), ABI.u32("amount", weight)
def transfer_to_account( self, to_account_id: int, amount: int, from_account_id: Union[str, int] = "genesis", session_contract: str = Contract.TRANSFER_TO_ACCOUNT_IT, payment_contract: str = Contract.STANDARD_PAYMENT, payment_args: bytes = MAX_PAYMENT_ABI, gas_price: int = 1, is_deploy_error_check: bool = True, ) -> str: """ Performs a transfer using the from account if given (or genesis if not) :param to_account_id: 1-20 index of test account for transfer into :param amount: amount of motes to transfer (mote = smallest unit of token) :param from_account_id: default 'genesis' account, but previously funded account_id is also valid. :param session_contract: session contract to execute. :param payment_contract: Payment contract to execute. :param payment_args: Payment Amount ABI :param gas_price: Gas price :param is_deploy_error_check: Check that amount transfer is success. :returns block_hash in hex str """ logging.info(f"=== Transferring {amount} to {to_account_id}") assert ( is_valid_account(to_account_id) and to_account_id != "genesis" ), "Can transfer only to non-genesis accounts in test framework (1-20)." assert is_valid_account( from_account_id ), "Must transfer from a valid account_id: 1-20 or 'genesis'" from_account = Account(from_account_id) to_account = Account(to_account_id) session_args = ABI.args( [ ABI.account("account", to_account.public_key_binary), ABI.u32("amount", amount), ] ) response, deploy_hash_bytes = self.p_client.deploy( from_address=from_account.public_key_hex, session_contract=session_contract, payment_contract=payment_contract, public_key=from_account.public_key_path, private_key=from_account.private_key_path, gas_price=gas_price, session_args=session_args, payment_args=payment_args, ) deploy_hash_hex = deploy_hash_bytes.hex() assert len(deploy_hash_hex) == 64 response = self.p_client.propose() block_hash = response.block_hash.hex() assert len(deploy_hash_hex) == 64 if is_deploy_error_check: for deploy_info in self.p_client.show_deploys(block_hash): if deploy_info.is_error: raise Exception(f"transfer_to_account: {deploy_info.error_message}") return block_hash