class TestProtocol: """Basic tests for protocol.""" @allure.link(helpers.get_vcs_link()) def test_protocol_state_keys(self, cluster: clusterlib.ClusterLib): """Check output of `query protocol-state`.""" common.get_test_id(cluster) # TODO: the query is currently broken query_currently_broken = False try: protocol_state = cluster.get_protocol_state() except clusterlib.CLIError as err: if "currentlyBroken" not in str(err): raise query_currently_broken = True if query_currently_broken: pytest.xfail("`query protocol-state` is currently broken") assert tuple(sorted(protocol_state)) == PROTOCOL_STATE_KEYS @allure.link(helpers.get_vcs_link()) @pytest.mark.xfail def test_protocol_state_outfile(self, cluster: clusterlib.ClusterLib): """Check output file produced by `query protocol-state`.""" common.get_test_id(cluster) protocol_state: dict = json.loads( cluster.query_cli(["protocol-state", "--out-file", "/dev/stdout"])) assert tuple(sorted(protocol_state)) == PROTOCOL_STATE_KEYS @allure.link(helpers.get_vcs_link()) def test_protocol_params(self, cluster: clusterlib.ClusterLib): """Check output of `query protocol-parameters`.""" common.get_test_id(cluster) protocol_params = cluster.get_protocol_params() assert tuple(sorted(protocol_params.keys())) == PROTOCOL_PARAM_KEYS
class TestCLI: """Tests for cardano-cli.""" TX_BODY_FILE = DATA_DIR / "test_tx_metadata_both_tx.body" TX_FILE = DATA_DIR / "test_tx_metadata_both_tx.signed" TX_OUT = DATA_DIR / "test_tx_metadata_both_tx.out" @allure.link(helpers.get_vcs_link()) @pytest.mark.testnets @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_protocol_mode(self, cluster: clusterlib.ClusterLib): """Check the default protocol mode - command works even without specifying protocol mode.""" if cluster.protocol != clusterlib.Protocols.CARDANO: pytest.skip("runs on cluster in full cardano mode") cluster.cli( [ "query", "utxo", "--address", "addr_test1vpst87uzwafqkxumyf446zr2jsyn44cfpu9fe8yqanyuh6glj2hkl", *cluster.magic_args, ] ) @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_whole_utxo(self, cluster: clusterlib.ClusterLib): """Check that it is possible to return the whole UTxO on local cluster.""" if cluster.protocol != clusterlib.Protocols.CARDANO: pytest.skip("runs on cluster in full cardano mode") cluster.cli( [ "query", "utxo", "--whole-utxo", *cluster.magic_args, ] ) @allure.link(helpers.get_vcs_link()) @pytest.mark.testnets def test_tx_view(self, cluster: clusterlib.ClusterLib): """Check that the output of `transaction view` is as expected.""" tx_body = cluster.view_tx(tx_body_file=self.TX_BODY_FILE) tx = cluster.view_tx(tx_file=self.TX_FILE) assert tx_body == tx with open(self.TX_OUT) as infile: tx_view_out = infile.read() assert tx == tx_view_out.strip()
class TestKey: """Tests for cardano-cli key.""" @allure.link(helpers.get_vcs_link()) def test_non_extended_key_valid(self, cluster: clusterlib.ClusterLib): """Check that the non-extended verification key is according the verification key.""" temp_template = common.get_test_id(cluster) # get an extended verification key payment_keys = cluster.gen_payment_key_pair( key_name=f"{temp_template}_extended", extended=True ) with open(payment_keys.vkey_file, encoding="utf-8") as in_file: # ignore the first 4 chars, just an informative keyword extended_vkey = json.loads(in_file.read().strip()).get("cborHex", "")[4:] # get a non-extended verification key using the extended key non_extended_key_file = cluster.gen_non_extended_verification_key( key_name=temp_template, extended_verification_key_file=payment_keys.vkey_file ) with open(non_extended_key_file, encoding="utf-8") as in_file: # ignore the first 4 chars, just an informative keyword non_extended_vkey = json.loads(in_file.read().strip()).get("cborHex", "")[4:] assert extended_vkey.startswith(non_extended_vkey) @allure.link(helpers.get_vcs_link()) def test_non_extended_key_error(self, cluster: clusterlib.ClusterLib): """Try to get a non-extended verification key with a signing key file. Expect failure. Should only allow extended verification key files. """ temp_template = common.get_test_id(cluster) # get an extended key payment_keys = cluster.gen_payment_key_pair( key_name=f"{temp_template}_extended", extended=True ) # try to get a non-extended verification key using the extended signing key with pytest.raises(clusterlib.CLIError) as excinfo: cluster.gen_non_extended_verification_key( key_name=temp_template, extended_verification_key_file=payment_keys.skey_file ) assert "TextEnvelope type error: Expected one of:" in str(excinfo.value)
class TestProtocol: """Basic tests for protocol.""" @allure.link(helpers.get_vcs_link()) def test_protocol_state_keys(self, cluster: clusterlib.ClusterLib): """Check output of `query protocol-state`.""" protocol_state = cluster.get_protocol_state() assert tuple(sorted(protocol_state)) == PROTOCOL_STATE_KEYS @allure.link(helpers.get_vcs_link()) def test_protocol_state_outfile(self, cluster: clusterlib.ClusterLib): """Check output file produced by `query protocol-state`.""" protocol_state: dict = json.loads( cluster.query_cli(["protocol-state", "--out-file", "/dev/stdout"])) assert tuple(sorted(protocol_state)) == PROTOCOL_STATE_KEYS @allure.link(helpers.get_vcs_link()) def test_protocol_params(self, cluster: clusterlib.ClusterLib): """Check output of `query protocol-parameters`.""" protocol_params = cluster.get_protocol_params() assert tuple(sorted(protocol_params.keys())) == PROTOCOL_PARAM_KEYS
class TestLedgerState: """Basic tests for ledger state.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_ledger_state_keys(self, cluster: clusterlib.ClusterLib): """Check output of `query ledger-state`.""" ledger_state = clusterlib_utils.get_ledger_state(cluster_obj=cluster) assert tuple(sorted(ledger_state)) == LEDGER_STATE_KEYS
class TestBasic: """Basic tests for node configuration.""" @allure.link(helpers.get_vcs_link()) def test_epoch_length(self, cluster_epoch_length: clusterlib.ClusterLib): """Test the *epochLength* configuration.""" cluster = cluster_epoch_length assert cluster.slot_length == 0.2 assert cluster.epoch_length == 1500 check_epoch_length(cluster) @allure.link(helpers.get_vcs_link()) @pytest.mark.order(2) def test_slot_length(self, cluster_slot_length: clusterlib.ClusterLib): """Test the *slotLength* configuration.""" cluster = cluster_slot_length assert cluster.slot_length == 0.3 assert cluster.epoch_length == 1000 check_epoch_length(cluster)
class TestCLI: """Tests for cardano-cli.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_protocol_mode(self, cluster: clusterlib.ClusterLib): """Check the default protocol mode - command works even without specifying protocol mode.""" if cluster.protocol != clusterlib.Protocols.CARDANO: pytest.skip("runs on cluster in full cardano mode") cluster.cli(["query", "utxo", *cluster.magic_args])
class TestUpdateProposal: """Tests for update proposal.""" @pytest.fixture def cluster_update_proposal( self, cluster_manager: cluster_management.ClusterManager, ) -> clusterlib.ClusterLib: return cluster_manager.get(singleton=True, cleanup=True) @pytest.fixture def payment_addr( self, cluster_manager: cluster_management.ClusterManager, cluster_update_proposal: clusterlib.ClusterLib, ) -> clusterlib.AddressRecord: """Create new payment address.""" cluster = cluster_update_proposal with cluster_manager.cache_fixture() as fixture_cache: if fixture_cache.value: return fixture_cache.value # type: ignore addr = clusterlib_utils.create_payment_addr_records( f"addr_test_update_proposal_ci{cluster_manager.cluster_instance}_0", cluster_obj=cluster, )[0] fixture_cache.value = addr # fund source addresses clusterlib_utils.fund_from_faucet( addr, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) return addr @allure.link(helpers.get_vcs_link()) def test_update_proposal(self, cluster_update_proposal: clusterlib.ClusterLib, payment_addr: clusterlib.AddressRecord): """Test changing *decentralisationParam* using update proposal .""" clusterlib_utils.update_params( cluster_obj=cluster_update_proposal, src_addr_record=payment_addr, update_proposals=[ clusterlib_utils.UpdateProposal( arg="--decentralization-parameter", value=0.5, name="decentralization", ) ], )
class TestDBSync: """General db-sync tests.""" DBSYNC_TABLES = { "ada_pots", "admin_user", "block", "collateral_tx_in", "delegation", "epoch", "epoch_param", "epoch_stake", "epoch_sync_time", "ma_tx_mint", "ma_tx_out", "meta", "orphaned_reward", "param_proposal", "pool_hash", "pool_metadata_ref", "pool_offline_data", "pool_offline_fetch_error", "pool_owner", "pool_relay", "pool_retire", "pool_update", "pot_transfer", "reserve", "reserved_pool_ticker", "reward", "schema_version", "slot_leader", "stake_address", "stake_deregistration", "stake_registration", "treasury", "tx", "tx_in", "tx_metadata", "tx_out", "withdrawal", } @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( VERSIONS.dbsync < version.parse("10.0.0"), reason="needs db-sync version >= 10.0.0", ) def test_table_names(self, cluster): """Check that all the expected tables are present in db-sync.""" # pylint: disable=unused-argument assert self.DBSYNC_TABLES.issubset(dbsync_utils.query_table_names())
class TestEKG: """EKG metrics tests.""" @allure.link(helpers.get_vcs_link()) def test_available_metrics( self, wait_epochs, ): """Test that available EKG metrics matches the expected schema.""" # pylint: disable=unused-argument ekg_port = (cluster_nodes.get_cluster_type().cluster_scripts. get_instance_ports( cluster_nodes.get_instance_num()).ekg_pool1) response = get_ekg_metrics(ekg_port) model_ekg.Model.validate(response.json())
class TestBasic: """Basic tests for update proposal.""" @pytest.fixture def payment_addr( self, cluster_manager: parallel_run.ClusterManager, cluster_update_proposal: clusterlib.ClusterLib, ) -> clusterlib.AddressRecord: """Create new payment address.""" data_key = id(TestBasic) cached_value = cluster_manager.cache.test_data.get(data_key) if cached_value: return cached_value # type: ignore cluster = cluster_update_proposal addr = clusterlib_utils.create_payment_addr_records( f"addr_test_basic_update_proposal_ci{cluster_manager.cluster_instance}_0", cluster_obj=cluster, )[0] cluster_manager.cache.test_data[data_key] = addr # fund source addresses clusterlib_utils.fund_from_faucet( addr, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) return addr @allure.link(helpers.get_vcs_link()) def test_update_proposal( self, cluster_update_proposal: clusterlib.ClusterLib, payment_addr: clusterlib.AddressRecord ): """Test changing *decentralisationParam* using update proposal .""" clusterlib_utils.update_params( cluster_obj=cluster_update_proposal, src_addr_record=payment_addr, update_proposals=[ clusterlib_utils.UpdateProposal( arg="--decentralization-parameter", value=0.5, name="decentralisationParam", ) ], )
class TestPrometheus: """Prometheus metrics tests.""" @allure.link(helpers.get_vcs_link()) def test_available_metrics( self, wait_epochs, ): """Test that list of available metrics == list of expected metrics.""" # pylint: disable=unused-argument prometheus_port = (cluster_nodes.get_cluster_type( ).cluster_scripts.get_instance_ports( cluster_nodes.get_cluster_env().instance_num).prometheus_pool1) response = get_prometheus_metrics(prometheus_port) metrics = response.text.strip().split("\n") metrics_keys = sorted(m.split(" ")[0] for m in metrics) assert metrics_keys == EXPECTED_METRICS, "Metrics differ"
class TestPoolSaturation: @allure.link(helpers.get_vcs_link()) def test_oversaturated( # noqa: C901 self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pools: clusterlib.ClusterLib, ): """Check diminished rewards when stake pool is oversaturated. The stake pool continues to operate normally and those who delegate to that pool receive rewards, but the rewards are proportionally lower than those received from stake pool that is not oversaturated. * register and delegate stake address in "init epoch", for all available pools * in "init epoch" + 2, saturate all available pools (block distribution remains balanced among pools) * in "init epoch" + 3, oversaturate one pool * in "init epoch" + 5, for all available pools, withdraw rewards and transfer funds from delegated addresses so pools are no longer (over)saturated * while doing the steps above, collect rewards data for 9 epochs * compare proportionality of rewards in epochs where pools were non-saturated, saturated and oversaturated """ # pylint: disable=too-many-statements,too-many-locals,too-many-branches epoch_saturate = 2 epoch_oversaturate = 4 epoch_withdrawal = 6 cluster = cluster_lock_pools temp_template = common.get_test_id(cluster) initial_balance = 1_000_000_000 faucet_rec = cluster_manager.cache.addrs_data["byron000"] pool_records: Dict[int, PoolRecord] = {} # make sure we have enough time to finish the delegation in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) init_epoch = cluster.get_epoch() # submit registration certificates and delegate to pools for idx, res in enumerate( [ cluster_management.Resources.POOL1, cluster_management.Resources.POOL2, cluster_management.Resources.POOL3, ], start=1, ): pool_addrs_data = cluster_manager.cache.addrs_data[res] reward_addr = clusterlib.PoolUser( payment=pool_addrs_data["payment"], stake=pool_addrs_data["reward"]) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=res, ) pool_id_dec = helpers.decode_bech32(bech32=pool_id) delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=f"{temp_template}_pool{idx}", pool_id=pool_id, amount=initial_balance, ) pool_records[idx] = PoolRecord( name=res, id=pool_id, id_dec=pool_id_dec, reward_addr=reward_addr, delegation_out=delegation_out, user_rewards=[], owner_rewards=[], blocks_minted={}, saturation_amounts={}, ) # record initial reward balance for each pool for pool_rec in pool_records.values(): user_payment_balance = cluster.get_address_balance( pool_rec.delegation_out.pool_user.payment.address) owner_payment_balance = cluster.get_address_balance( pool_rec.reward_addr.payment.address) pool_rec.user_rewards.append( RewardRecord( epoch_no=init_epoch, reward_total=0, reward_per_epoch=0, stake_total=user_payment_balance, )) pool_rec.owner_rewards.append( RewardRecord( epoch_no=init_epoch, reward_total=cluster.get_stake_addr_info( pool_rec.reward_addr.stake.address). reward_account_balance, reward_per_epoch=0, stake_total=owner_payment_balance, )) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Checking rewards for 10 epochs.") for __ in range(10): # wait for new epoch if cluster.get_epoch( ) == pool_records[2].owner_rewards[-1].epoch_no: cluster.wait_for_new_epoch() # sleep till the end of epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=-50, stop=-40, force_epoch=True) this_epoch = cluster.get_epoch() ledger_state = clusterlib_utils.get_ledger_state( cluster_obj=cluster) clusterlib_utils.save_ledger_state( cluster_obj=cluster, state_name=f"{temp_template}_{this_epoch}", ledger_state=ledger_state, ) for pool_rec in pool_records.values(): # reward balance in previous epoch prev_user_reward = pool_rec.user_rewards[-1].reward_total prev_owner_reward = pool_rec.owner_rewards[-1].reward_total pool_rec.blocks_minted[this_epoch - 1] = (ledger_state["blocksBefore"].get( pool_rec.id_dec) or 0) # current reward balance user_reward = cluster.get_stake_addr_info( pool_rec.delegation_out.pool_user.stake.address ).reward_account_balance owner_reward = cluster.get_stake_addr_info( pool_rec.reward_addr.stake.address).reward_account_balance # total reward amounts received this epoch owner_reward_epoch = owner_reward - prev_owner_reward # We cannot compare with previous rewards in epochs where # `this_epoch >= init_epoch + epoch_withdrawal`. # There's a withdrawal of rewards at the end of these epochs. if this_epoch > init_epoch + epoch_withdrawal: user_reward_epoch = user_reward else: user_reward_epoch = user_reward - prev_user_reward # store collected rewards info user_payment_balance = cluster.get_address_balance( pool_rec.delegation_out.pool_user.payment.address) owner_payment_balance = cluster.get_address_balance( pool_rec.reward_addr.payment.address) pool_rec.user_rewards.append( RewardRecord( epoch_no=this_epoch, reward_total=user_reward, reward_per_epoch=user_reward_epoch, stake_total=user_payment_balance + user_reward, )) pool_rec.owner_rewards.append( RewardRecord( epoch_no=this_epoch, reward_total=owner_reward, reward_per_epoch=owner_reward_epoch, stake_total=owner_payment_balance, )) pool_rec.saturation_amounts[ this_epoch] = _get_saturation_threshold( cluster_obj=cluster, ledger_state=ledger_state, pool_id=pool_rec.id) # fund the delegated addresses - saturate all pools if this_epoch == init_epoch + epoch_saturate: clusterlib_utils.fund_from_faucet( *[ p.delegation_out.pool_user.payment for p in pool_records.values() ], cluster_obj=cluster, faucet_data=faucet_rec, amount=[ p.saturation_amounts[this_epoch] - 100_000_000_000 for p in pool_records.values() ], tx_name=f"{temp_template}_saturate_pools_ep{this_epoch}", force=True, ) with cluster_manager.restart_on_failure(): # Fund the address delegated to "pool2" to oversaturate the pool. # New stake amount will be current (saturated) stake * 2. if this_epoch == init_epoch + epoch_oversaturate: assert (pool_records[2].saturation_amounts[this_epoch] > 0), "Pool is already saturated" current_stake = int( cluster.get_stake_snapshot( pool_records[2].id)["poolStakeMark"]) overstaturate_amount = current_stake * 2 saturation_threshold = pool_records[2].saturation_amounts[ this_epoch] assert overstaturate_amount > saturation_threshold, ( f"{overstaturate_amount} Lovelace is not enough to oversature the pool " f"({saturation_threshold} is needed)") clusterlib_utils.fund_from_faucet( pool_records[2].delegation_out.pool_user.payment, cluster_obj=cluster, faucet_data=faucet_rec, amount=overstaturate_amount, tx_name=f"{temp_template}_oversaturate_pool2", force=True, ) # wait 4 epochs for first rewards if this_epoch >= init_epoch + 4: assert (owner_reward > prev_owner_reward ), "New reward was not received by pool owner" # transfer funds back to faucet so the pools are no longer (over)saturated # and staked amount is +- same as the `initial_balance` if this_epoch >= init_epoch + epoch_withdrawal: _withdraw_rewards( *[ p.delegation_out.pool_user for p in pool_records.values() ], cluster_obj=cluster, tx_name=f"{temp_template}_ep{this_epoch}", ) return_to_addrs = [] return_amounts = [] for idx, pool_rec in pool_records.items(): deleg_payment_balance = cluster.get_address_balance( pool_rec.delegation_out.pool_user.payment.address) if deleg_payment_balance > initial_balance + 10_000_000: return_to_addrs.append( pool_rec.delegation_out.pool_user.payment) return_amounts.append(deleg_payment_balance - initial_balance) clusterlib_utils.return_funds_to_faucet( *return_to_addrs, cluster_obj=cluster, faucet_addr=faucet_rec["payment"].address, amount=return_amounts, tx_name=f"{temp_template}_ep{this_epoch}", ) for return_addr in return_to_addrs: deleg_payment_balance = cluster.get_address_balance( return_addr.address) assert ( deleg_payment_balance <= initial_balance ), "Unexpected funds in payment address '{return_addr}'" assert ( cluster.get_epoch() == this_epoch ), "Failed to finish actions in single epoch, it would affect other checks" pool1_user_rewards_per_block = _get_reward_per_block(pool_records[1]) pool2_user_rewards_per_block = _get_reward_per_block(pool_records[2]) pool3_user_rewards_per_block = _get_reward_per_block(pool_records[3]) pool1_owner_rewards_per_block = _get_reward_per_block( pool_records[1], owner_rewards=True) pool2_owner_rewards_per_block = _get_reward_per_block( pool_records[2], owner_rewards=True) pool3_owner_rewards_per_block = _get_reward_per_block( pool_records[3], owner_rewards=True) oversaturated_epoch = max( e for e, r in pool_records[2].saturation_amounts.items() if r < 0) saturated_epoch = oversaturated_epoch - 2 nonsaturated_epoch = oversaturated_epoch - 4 try: # check that rewards per block per stake for "pool2" in the epoch where the pool is # oversaturated is lower than in epochs where pools are not oversaturated assert (pool1_user_rewards_per_block[nonsaturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) assert (pool2_user_rewards_per_block[nonsaturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) assert (pool3_user_rewards_per_block[nonsaturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) assert (pool1_user_rewards_per_block[saturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) assert (pool2_user_rewards_per_block[saturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) assert (pool3_user_rewards_per_block[saturated_epoch] > pool2_user_rewards_per_block[oversaturated_epoch]) # check that oversaturated pool doesn't lead to increased rewards for pool owner # when compared to saturated pool, i.e. total pool margin amount is not increased pool1_rew_fraction_sat = pool1_owner_rewards_per_block[ saturated_epoch] pool2_rew_fraction_sat = pool2_owner_rewards_per_block[ saturated_epoch] pool3_rew_fraction_sat = pool3_owner_rewards_per_block[ saturated_epoch] pool2_rew_fraction_over = pool2_owner_rewards_per_block[ oversaturated_epoch] assert pool2_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval( pool2_rew_fraction_sat, pool2_rew_fraction_over, frac=0.4, ) assert pool1_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval( pool1_rew_fraction_sat, pool2_rew_fraction_over, frac=0.4, ) assert pool3_rew_fraction_sat > pool2_rew_fraction_over or helpers.is_in_interval( pool3_rew_fraction_sat, pool2_rew_fraction_over, frac=0.4, ) # Compare rewards in last (non-saturated) epoch to rewards in next-to-last # (saturated / over-saturated) epoch. # This way check that staked amount for each pool was restored to `initial_balance` # and that rewards correspond to the restored amounts. for pool_rec in pool_records.values(): assert (pool_rec.user_rewards[-1].reward_per_epoch * 100 < pool_rec.user_rewards[-2].reward_per_epoch) except Exception: # save debugging data in case of test failure with open(f"{temp_template}_pool_records.pickle", "wb") as out_data: pickle.dump(pool_records, out_data) raise
class TestNegativeInlineDatum: """Tests for Tx output with inline datum that are expected to fail.""" @allure.link(helpers.get_vcs_link()) @hypothesis.given(datum_value=st.text()) @common.hypothesis_settings() def test_lock_tx_invalid_datum( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], datum_value: str, ): """Test locking a Tx output with an invalid datum. Expect failure. """ temp_template = common.get_test_id(cluster) amount = 2_000_000 datum_file = f"{temp_template}.datum" with open(datum_file, "w", encoding="utf-8") as outfile: json.dump(f'{{"{datum_value}"}}', outfile) plutus_op = plutus_common.PlutusOp( script_file=plutus_common.ALWAYS_SUCCEEDS_PLUTUS_V2, datum_file=Path(datum_file), redeemer_cbor_file=plutus_common.REDEEMER_42_CBOR, execution_cost=plutus_common.ALWAYS_SUCCEEDS_COST, ) # for mypy assert plutus_op.execution_cost redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) # create a Tx output with an invalid inline datum at the script address with pytest.raises(clusterlib.CLIError) as excinfo: _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=True, ) err_str = str(excinfo.value) assert "JSON object expected. Unexpected value" in err_str, err_str @allure.link(helpers.get_vcs_link()) def test_lock_tx_v1_script( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], ): """Test locking a Tx output with an inline datum and a v1 script. Expect failure. """ __: Any # mypy workaround temp_template = common.get_test_id(cluster) amount = 2_000_000 plutus_op = plutus_common.PlutusOp( script_file=plutus_common.ALWAYS_SUCCEEDS_PLUTUS_V1, datum_file=plutus_common.DATUM_42_TYPED, redeemer_cbor_file=plutus_common.REDEEMER_42_CBOR, execution_cost=plutus_common.ALWAYS_SUCCEEDS_COST, ) # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) script_utxos, collateral_utxos, __, __ = _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=True, ) plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, script_file=plutus_op.script_file, collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), redeemer_cbor_file=plutus_op.redeemer_cbor_file, inline_datum_present=True, ) ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.submit_tx( tx_file=tx_signed_redeem, txins=[t.txins[0] for t in tx_output_redeem.script_txins if t.txins], ) err_str = str(excinfo.value) assert "InlineDatumsNotSupported" in err_str, err_str @allure.link(helpers.get_vcs_link()) @hypothesis.given(datum_content=st.text(alphabet=string.ascii_letters, min_size=65)) @common.hypothesis_settings() def test_lock_tx_big_datum( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], datum_content: str, ): """Test locking a Tx output with a datum bigger than the allowed size. Expect failure. """ hypothesis.assume(datum_content) temp_template = common.get_test_id(cluster) amount = 2_000_000 plutus_op = plutus_common.PlutusOp( script_file=plutus_common.ALWAYS_SUCCEEDS_PLUTUS_V2, datum_value=f'"{datum_content}"', redeemer_cbor_file=plutus_common.REDEEMER_42_CBOR, execution_cost=plutus_common.ALWAYS_SUCCEEDS_COST, ) # for mypy assert plutus_op.execution_cost redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) with pytest.raises(clusterlib.CLIError) as excinfo: _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=True, ) err_str = str(excinfo.value) assert "Byte strings in script data must consist of at most 64 bytes" in err_str, err_str @allure.link(helpers.get_vcs_link()) def test_lock_tx_datum_as_witness( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord] ): """Test unlock a Tx output with a datum as witness. Expect failure. """ __: Any # mypy workaround temp_template = common.get_test_id(cluster) amount = 2_000_000 plutus_op = PLUTUS_OP_ALWAYS_SUCCEEDS # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) script_utxos, collateral_utxos, __, __ = _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=True, ) plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, script_file=plutus_op.script_file, collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), redeemer_cbor_file=plutus_op.redeemer_cbor_file, datum_file=plutus_op.datum_file, ) ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.submit_tx( tx_file=tx_signed_redeem, txins=[t.txins[0] for t in tx_output_redeem.script_txins if t.txins], ) err_str = str(excinfo.value) assert "NonOutputSupplimentaryDatums" in err_str, err_str
class TestUpdateProposals: """Tests for update proposals.""" @pytest.fixture def cluster_update_proposal( self, cluster_manager: cluster_management.ClusterManager, ) -> clusterlib.ClusterLib: return cluster_manager.get( lock_resources=[cluster_management.Resources.CLUSTER], cleanup=True) @pytest.fixture def payment_addr( self, cluster_manager: cluster_management.ClusterManager, cluster_update_proposal: clusterlib.ClusterLib, ) -> clusterlib.AddressRecord: """Create new payment address.""" cluster = cluster_update_proposal with cluster_manager.cache_fixture() as fixture_cache: if fixture_cache.value: return fixture_cache.value # type: ignore addr = clusterlib_utils.create_payment_addr_records( f"addr_test_update_proposal_ci{cluster_manager.cluster_instance_num}_0", cluster_obj=cluster, )[0] fixture_cache.value = addr # fund source addresses clusterlib_utils.fund_from_faucet( addr, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) return addr @allure.link(helpers.get_vcs_link()) def test_update_proposal( self, cluster_update_proposal: clusterlib.ClusterLib, payment_addr: clusterlib.AddressRecord, ): """Test changing protocol parameters using update proposal. * if era >= Alonzo, update Alonzo-specific parameters and: - wait for next epoch - check that parameters were updated * submit update proposal * in the same epoch, submit another update proposal * wait for next epoch * check that parameters were updated with the values submitted in the second update proposal, i.e. the second update proposal overwritten the first one """ cluster = cluster_update_proposal temp_template = common.get_test_id(cluster) max_tx_execution_units = 11_000_000_000 max_block_execution_units = 110_000_000_000 price_execution_steps = "12/10" price_execution_memory = "1.3" this_epoch = cluster.wait_for_new_epoch() protocol_params = cluster.get_protocol_params() with open(f"{temp_template}_pparams_ep{this_epoch}.json", "w", encoding="utf-8") as fp_out: json.dump(protocol_params, fp_out, indent=4) # update Alonzo-speciffic parameters in separate update proposal if VERSIONS.cluster_era >= VERSIONS.ALONZO: if VERSIONS.cluster_era >= VERSIONS.BABBAGE: utxo_cost = clusterlib_utils.UpdateProposal( arg="--utxo-cost-per-word", value=8001, name="", # needs custom check ) else: utxo_cost = clusterlib_utils.UpdateProposal( arg="--utxo-cost-per-word", value=8001, name="utxoCostPerWord", ) update_proposals_alonzo = [ utxo_cost, clusterlib_utils.UpdateProposal( arg="--max-value-size", value=5000, name="maxValueSize", ), clusterlib_utils.UpdateProposal( arg="--collateral-percent", value=90, name="collateralPercentage", ), clusterlib_utils.UpdateProposal( arg="--max-collateral-inputs", value=4, name="maxCollateralInputs", ), clusterlib_utils.UpdateProposal( arg="--max-tx-execution-units", value= f"({max_tx_execution_units},{max_tx_execution_units})", name="", # needs custom check ), clusterlib_utils.UpdateProposal( arg="--max-block-execution-units", value= f"({max_block_execution_units},{max_block_execution_units})", name="", # needs custom check ), clusterlib_utils.UpdateProposal( arg="--price-execution-steps", value=price_execution_steps, name="", # needs custom check ), clusterlib_utils.UpdateProposal( arg="--price-execution-memory", value=price_execution_memory, name="", # needs custom check ), ] clusterlib_utils.update_params_build( cluster_obj=cluster, src_addr_record=payment_addr, update_proposals=update_proposals_alonzo, ) this_epoch = cluster.wait_for_new_epoch() protocol_params = cluster.get_protocol_params() with open(f"{temp_template}_pparams_ep{this_epoch}.json", "w", encoding="utf-8") as fp_out: json.dump(protocol_params, fp_out, indent=4) clusterlib_utils.check_updated_params( update_proposals=update_proposals_alonzo, protocol_params=protocol_params) assert protocol_params["maxTxExecutionUnits"][ "memory"] == max_tx_execution_units assert protocol_params["maxTxExecutionUnits"][ "steps"] == max_tx_execution_units assert protocol_params["maxBlockExecutionUnits"][ "memory"] == max_block_execution_units assert protocol_params["maxBlockExecutionUnits"][ "steps"] == max_block_execution_units assert protocol_params["executionUnitPrices"]["priceSteps"] == 1.2 assert protocol_params["executionUnitPrices"]["priceMemory"] == 1.3 if VERSIONS.cluster_era >= VERSIONS.BABBAGE: # the resulting number will be multiple of 8, i.e. 8000 assert protocol_params["utxoCostPerWord"] == math.floor( utxo_cost.value / 8) * 8 else: assert protocol_params["utxoCostPerWord"] == utxo_cost.value # Check that only one update proposal can be applied each epoch and that the last # update proposal cancels the previous one. Following parameter values will be # overwritten by the next update proposal. update_proposal_canceled = [ clusterlib_utils.UpdateProposal( arg="--min-fee-linear", value=47, name="txFeePerByte", ), clusterlib_utils.UpdateProposal( arg="--pool-reg-deposit", value=410_000_000, name="stakePoolDeposit", ), clusterlib_utils.UpdateProposal( arg="--decentralization-parameter", value=0.2, name="decentralization", ), clusterlib_utils.UpdateProposal( arg="--pool-retirement-epoch-boundary", value=18, name="poolRetireMaxEpoch", ), clusterlib_utils.UpdateProposal( arg="--number-of-pools", value=10, name="stakePoolTargetNum", ), clusterlib_utils.UpdateProposal( arg="--max-block-body-size", value=65_555, name="maxBlockBodySize", ), clusterlib_utils.UpdateProposal( arg="--max-tx-size", value=16_400, name="maxTxSize", ), clusterlib_utils.UpdateProposal( arg="--min-pool-cost", value=2, name="minPoolCost", ), clusterlib_utils.UpdateProposal( arg="--max-block-header-size", value=1_400, name="maxBlockHeaderSize", ), clusterlib_utils.UpdateProposal( arg="--min-fee-constant", value=155_390, name="txFeeFixed", ), clusterlib_utils.UpdateProposal( arg="--key-reg-deposit-amt", value=300_050, name="stakeAddressDeposit", ), clusterlib_utils.UpdateProposal( arg="--pool-influence", value=0.5, name="poolPledgeInfluence", ), ] clusterlib_utils.update_params( cluster_obj=cluster, src_addr_record=payment_addr, update_proposals=update_proposal_canceled, ) time.sleep(2) # the final update proposal decentralization = clusterlib_utils.UpdateProposal( arg="--decentralization-parameter", value=0.1, name="", # needs custom check ) update_proposals = [ decentralization, clusterlib_utils.UpdateProposal( arg="--min-fee-linear", value=45, name="txFeePerByte", ), clusterlib_utils.UpdateProposal( arg="--pool-reg-deposit", value=400_000_000, name="stakePoolDeposit", ), clusterlib_utils.UpdateProposal( arg="--pool-retirement-epoch-boundary", value=19, name="poolRetireMaxEpoch", ), clusterlib_utils.UpdateProposal( arg="--number-of-pools", value=9, name="stakePoolTargetNum", ), clusterlib_utils.UpdateProposal( arg="--max-block-body-size", value=65_544, name="maxBlockBodySize", ), clusterlib_utils.UpdateProposal( arg="--max-tx-size", value=16_392, name="maxTxSize", ), clusterlib_utils.UpdateProposal( arg="--min-pool-cost", value=1, name="minPoolCost", ), clusterlib_utils.UpdateProposal( arg="--max-block-header-size", value=1_200, name="maxBlockHeaderSize", ), clusterlib_utils.UpdateProposal( arg="--min-fee-constant", value=155_380, name="txFeeFixed", ), clusterlib_utils.UpdateProposal( arg="--key-reg-deposit-amt", value=300_000, name="stakeAddressDeposit", ), clusterlib_utils.UpdateProposal( arg="--pool-influence", value=0.4, name="poolPledgeInfluence", ), ] if VERSIONS.cluster_era < VERSIONS.ALONZO: update_proposals.append( clusterlib_utils.UpdateProposal( arg="--min-utxo-value", value=2, name="minUTxOValue", )) clusterlib_utils.update_params( cluster_obj=cluster, src_addr_record=payment_addr, update_proposals=update_proposals, ) this_epoch = cluster.wait_for_new_epoch() protocol_params = cluster.get_protocol_params() with open(f"{temp_template}_pparams_ep{this_epoch}.json", "w", encoding="utf-8") as fp_out: json.dump(protocol_params, fp_out, indent=4) clusterlib_utils.check_updated_params( update_proposals=update_proposals, protocol_params=protocol_params) if VERSIONS.cluster_era >= VERSIONS.BABBAGE: assert protocol_params["decentralization"] is None else: assert protocol_params[ "decentralization"] == decentralization.value
class TestPrometheus: """Prometheus metrics tests.""" EXPECTED_METRICS = [ "cardano_node_metrics_Forge_adopted_int", "cardano_node_metrics_Forge_forge_about_to_lead_int", "cardano_node_metrics_Forge_forged_int", "cardano_node_metrics_Forge_node_is_leader_int", "cardano_node_metrics_Forge_node_not_leader_int", "cardano_node_metrics_Mem_resident_int", "cardano_node_metrics_RTS_gcLiveBytes_int", "cardano_node_metrics_RTS_gcMajorNum_int", "cardano_node_metrics_RTS_gcMinorNum_int", "cardano_node_metrics_RTS_gcticks_int", "cardano_node_metrics_RTS_mutticks_int", "cardano_node_metrics_Stat_cputicks_int", "cardano_node_metrics_Stat_threads_int", "cardano_node_metrics_blockNum_int", "cardano_node_metrics_blocksForgedNum_int", "cardano_node_metrics_currentKESPeriod_int", "cardano_node_metrics_delegMapSize_int", "cardano_node_metrics_density_real", "cardano_node_metrics_epoch_int", "cardano_node_metrics_mempoolBytes_int", "cardano_node_metrics_myBlocksUncoupled_int", "cardano_node_metrics_nodeIsLeaderNum_int", "cardano_node_metrics_nodeStartTime_int", "cardano_node_metrics_operationalCertificateExpiryKESPeriod_int", "cardano_node_metrics_operationalCertificateStartKESPeriod_int", "cardano_node_metrics_remainingKESPeriods_int", "cardano_node_metrics_served_header_counter_int", "cardano_node_metrics_slotInEpoch_int", "cardano_node_metrics_slotNum_int", "cardano_node_metrics_txsInMempool_int", "cardano_node_metrics_txsProcessedNum_int", "cardano_node_metrics_utxoSize_int", "ekg_server_timestamp_ms", "rts_gc_bytes_allocated", "rts_gc_bytes_copied", "rts_gc_cpu_ms", "rts_gc_cumulative_bytes_used", "rts_gc_current_bytes_slop", "rts_gc_current_bytes_used", "rts_gc_gc_cpu_ms", "rts_gc_gc_wall_ms", "rts_gc_init_cpu_ms", "rts_gc_init_wall_ms", "rts_gc_max_bytes_slop", "rts_gc_max_bytes_used", "rts_gc_mutator_cpu_ms", "rts_gc_mutator_wall_ms", "rts_gc_num_bytes_usage_samples", "rts_gc_num_gcs", "rts_gc_par_avg_bytes_copied", "rts_gc_par_max_bytes_copied", "rts_gc_par_tot_bytes_copied", "rts_gc_peak_megabytes_allocated", "rts_gc_wall_ms", ] @allure.link(helpers.get_vcs_link()) def test_available_metrics( self, wait_epochs, ): """Test that list of available metrics == list of expected metrics.""" # pylint: disable=unused-argument prometheus_port = (cluster_nodes.get_cluster_type( ).cluster_scripts.get_instance_ports( cluster_nodes.get_instance_num()).prometheus_pool1) response = get_prometheus_metrics(prometheus_port) metrics = response.text.strip().split("\n") metrics_keys = sorted(m.split()[0] for m in metrics) assert metrics_keys == self.EXPECTED_METRICS, "Metrics differ"
class TestLockingV2: """Tests for Tx output locking using Plutus V2 smart contracts.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize("use_inline_datum", (True, False), ids=("inline_datum", "datum_file")) @pytest.mark.parametrize( "use_reference_script", (True, False), ids=("reference_script", "script_file") ) def test_txout_locking( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], use_inline_datum: bool, use_reference_script: bool, request: FixtureRequest, ): """Test combinations of inline datum and datum file + reference script and script file. * create the necessary Tx outputs * check that the expected amount was locked at the script address * spend the locked UTxO * check that the expected UTxOs were correctly spent """ temp_template = f"{common.get_test_id(cluster)}_{request.node.callspec.id}" amount = 2_000_000 plutus_op = PLUTUS_OP_ALWAYS_SUCCEEDS # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=cluster.get_protocol_params() ) # Step 1: fund the Plutus script script_utxos, collateral_utxos, reference_utxos, __ = _fund_script( temp_template=temp_template, cluster=cluster, payment_addr=payment_addrs[0], dst_addr=payment_addrs[1], plutus_op=plutus_op, amount=amount, redeem_cost=redeem_cost, use_inline_datum=use_inline_datum, use_reference_script=use_reference_script, ) plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, script_file=plutus_op.script_file if not use_reference_script else "", reference_txin=reference_utxos[0] if use_reference_script else None, reference_type=clusterlib.ScriptTypes.PLUTUS_V2 if use_reference_script else "", collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), redeemer_cbor_file=plutus_op.redeemer_cbor_file, inline_datum_present=use_inline_datum, datum_file=plutus_op.datum_file if not use_inline_datum else "", ) ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) dst_init_balance = cluster.get_address_balance(payment_addrs[1].address) cluster.submit_tx( tx_file=tx_signed_redeem, txins=[t.txins[0] for t in tx_output_redeem.script_txins if t.txins], ) assert ( cluster.get_address_balance(payment_addrs[1].address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{payment_addrs[1].address}`" script_utxos_lovelace = [u for u in script_utxos if u.coin == clusterlib.DEFAULT_COIN] for u in script_utxos_lovelace: assert not cluster.get_utxo( txin=f"{u.utxo_hash}#{u.utxo_ix}", coins=[clusterlib.DEFAULT_COIN] ), f"Inputs were NOT spent for `{u.address}`" if use_reference_script: assert cluster.get_utxo( txin=f"{reference_utxos[0].utxo_hash}#{reference_utxos[0].utxo_ix}", coins=[clusterlib.DEFAULT_COIN], ), "Reference input was spent"
class TestMinting: """Tests for minting and burning tokens.""" @allure.link(helpers.get_vcs_link()) def test_minting_and_burning_witnesses( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test minting and burning of tokens, sign the transaction using witnesses.""" expected_fee = 201141 temp_template = helpers.get_func_name() asset_name = f"couttscoin{clusterlib.get_rand_str(4)}" amount = 5 payment_vkey_files = [p.vkey_file for p in issuers_addrs] token_mint_addr = issuers_addrs[0] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], ) policyid = cluster.get_policyid(multisig_script) token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[token]), "The token already exists" token_mint = clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, ) # token minting tx_out_mint = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=[token_mint], temp_template=f"{temp_template}_mint", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning token_burn = token_mint._replace(amount=-amount) tx_out_burn = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=[token_burn], temp_template=f"{temp_template}_burn", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @allure.link(helpers.get_vcs_link()) def test_minting_and_burning_sign( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test minting and burning of tokens, sign the transaction using skeys.""" expected_fee = 188821 temp_template = helpers.get_func_name() asset_name = f"couttscoin{clusterlib.get_rand_str(4)}" amount = 5 token_mint_addr = issuers_addrs[0] issuer_addr = issuers_addrs[1] # create simple script keyhash = cluster.get_payment_vkey_hash(issuer_addr.vkey_file) script_content = {"keyHash": keyhash, "type": "sig"} script = Path(f"{temp_template}.script") with open(f"{temp_template}.script", "w") as out_json: json.dump(script_content, out_json) policyid = cluster.get_policyid(script) token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[token]), "The token already exists" token_mint = clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=[issuer_addr], token_mint_addr=token_mint_addr, script=script, ) # token minting tx_out_mint = clusterlib_utils.mint_or_burn_sign( cluster_obj=cluster, new_tokens=[token_mint], temp_template=f"{temp_template}_mint", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning token_burn = token_mint._replace(amount=-amount) tx_out_burn = clusterlib_utils.mint_or_burn_sign( cluster_obj=cluster, new_tokens=[token_burn], temp_template=f"{temp_template}_burn", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @pytest.mark.parametrize( "tokens_db", ( (5, 351093), (10, 538533), (50, 2038053), (100, 3912453), (1000, 288789), ), ) @allure.link(helpers.get_vcs_link()) def test_multi_minting_and_burning_witnesses( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord], multisig_script_policyid: Tuple[Path, str], tokens_db: Tuple[int, int], ): """Test minting and burning multiple different tokens, sign the TX using witnesses.""" temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(8) amount = 5 tokens_num, expected_fee = tokens_db token_mint_addr = issuers_addrs[0] script, policyid = multisig_script_policyid tokens_to_mint = [] for tnum in range(tokens_num): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=script, )) # token minting minting_args = { "cluster_obj": cluster, "new_tokens": tokens_to_mint, "temp_template": f"{temp_template}_mint", } if tokens_num >= 500: with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness(**minting_args) # type: ignore if tokens_num >= 1000: assert "(UtxoFailure (MaxTxSizeUTxO" in str(excinfo.value) else: assert "(UtxoFailure (OutputTooBigUTxO" in str(excinfo.value) return tx_out_mint = _mint_or_burn_witness(**minting_args) # type: ignore for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint] tx_out_burn = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_burn, temp_template=f"{temp_template}_burn", ) for t in tokens_to_burn: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @pytest.mark.parametrize( "tokens_db", ( (5, 288789), (10, 413749), (50, 1413429), (100, 2663029), (1000, 0), ), ) @allure.link(helpers.get_vcs_link()) def test_multi_minting_and_burning_sign( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord], simple_script_policyid: Tuple[Path, str], tokens_db: Tuple[int, int], ): """Test minting and burning multiple different tokens, sign the TX using skeys.""" temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(8) amount = 5 tokens_num, expected_fee = tokens_db token_mint_addr = issuers_addrs[0] issuer_addr = issuers_addrs[1] script, policyid = simple_script_policyid tokens_to_mint = [] for tnum in range(tokens_num): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=[issuer_addr], token_mint_addr=token_mint_addr, script=script, )) # token minting minting_args = { "cluster_obj": cluster, "new_tokens": tokens_to_mint, "temp_template": f"{temp_template}_mint", } if tokens_num >= 500: with pytest.raises(clusterlib.CLIError) as excinfo: clusterlib_utils.mint_or_burn_sign( **minting_args) # type: ignore if tokens_num >= 1000: assert "(UtxoFailure (MaxTxSizeUTxO" in str(excinfo.value) else: assert "(UtxoFailure (OutputTooBigUTxO" in str(excinfo.value) return tx_out_mint = clusterlib_utils.mint_or_burn_sign( **minting_args) # type: ignore for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint] tx_out_burn = clusterlib_utils.mint_or_burn_sign( cluster_obj=cluster, new_tokens=tokens_to_burn, temp_template=f"{temp_template}_burn", ) for t in tokens_to_burn: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @allure.link(helpers.get_vcs_link()) def test_minting_and_partial_burning( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test minting and partial burning of tokens.""" expected_fee = 201141 temp_template = helpers.get_func_name() asset_name = f"couttscoin{clusterlib.get_rand_str(4)}" amount = 50 payment_vkey_files = [p.vkey_file for p in issuers_addrs] token_mint_addr = issuers_addrs[0] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], ) policyid = cluster.get_policyid(multisig_script) token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[token]), "The token already exists" token_mint = clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, ) # token minting tx_out_mint = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=[token_mint], temp_template=f"{temp_template}_mint", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning burn_amount = amount - 10 token_burn = token_mint._replace(amount=-burn_amount) _mint_or_burn_witness( cluster_obj=cluster, new_tokens=[token_burn], temp_template=f"{temp_template}_burn", ) token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[token]) assert (token_utxo and token_utxo[0].amount == amount - burn_amount), "The token was not burned" # burn the rest of tokens final_burn = token_mint._replace(amount=-10) _mint_or_burn_witness( cluster_obj=cluster, new_tokens=[final_burn], temp_template=f"{temp_template}_burn", ) # check expected fee assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval"
class TestTransfer: """Tests for transfering tokens.""" @pytest.fixture def payment_addrs( self, cluster_manager: cluster_management.ClusterManager, cluster: clusterlib.ClusterLib, ) -> List[clusterlib.AddressRecord]: """Create new payment addresses.""" with cluster_manager.cache_fixture() as fixture_cache: if fixture_cache.value: return fixture_cache.value # type: ignore addrs = clusterlib_utils.create_payment_addr_records( *[ f"token_transfer_ci{cluster_manager.cluster_instance}_{i}" for i in range(10) ], cluster_obj=cluster, ) fixture_cache.value = addrs # fund source addresses clusterlib_utils.fund_from_faucet( addrs[0], cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=20_000_000, ) return addrs @pytest.fixture def new_token( self, cluster_manager: cluster_management.ClusterManager, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], ) -> clusterlib_utils.TokenRecord: with cluster_manager.cache_fixture() as fixture_cache: if fixture_cache.value: return fixture_cache.value # type: ignore rand = clusterlib.get_rand_str(4) temp_template = f"test_tx_new_token_{rand}" asset_name = f"couttscoin{rand}" new_tokens = clusterlib_utils.new_tokens( asset_name, cluster_obj=cluster, temp_template=temp_template, token_mint_addr=payment_addrs[0], issuer_addr=payment_addrs[1], amount=20_000_000, ) new_token = new_tokens[0] fixture_cache.value = new_token return new_token @pytest.mark.parametrize("amount", (1, 10, 200, 2000, 100_000)) @allure.link(helpers.get_vcs_link()) def test_transfer_tokens( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], new_token: clusterlib_utils.TokenRecord, amount: int, ): """Test sending tokens to payment address. * send tokens from 1 source address to 1 destination address * check expected token balances for both source and destination addresses * check fees in Lovelace """ temp_template = f"{helpers.get_func_name()}_{amount}" amount_lovelace = 10 src_address = new_token.token_mint_addr.address dst_address = payment_addrs[2].address src_init_balance = cluster.get_address_balance(src_address) src_init_balance_token = cluster.get_address_balance( src_address, coin=new_token.token) dst_init_balance_token = cluster.get_address_balance( dst_address, coin=new_token.token) destinations = [ clusterlib.TxOut(address=dst_address, amount=amount, coin=new_token.token), clusterlib.TxOut(address=dst_address, amount=amount_lovelace), ] tx_files = clusterlib.TxFiles( signing_key_files=[new_token.token_mint_addr.skey_file]) tx_raw_output = cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, ) cluster.wait_for_new_block(new_blocks=2) assert ( cluster.get_address_balance( src_address, coin=new_token.token) == src_init_balance_token - amount ), f"Incorrect token balance for source address `{src_address}`" assert ( cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - amount_lovelace ), f"Incorrect Lovelace balance for source address `{src_address}`" assert ( cluster.get_address_balance( dst_address, coin=new_token.token) == dst_init_balance_token + amount ), f"Incorrect token balance for destination address `{dst_address}`" @allure.link(helpers.get_vcs_link()) def test_transfer_multiple_tokens( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], new_token: clusterlib_utils.TokenRecord, ): """Test sending multiple different tokens to payment address. * send multiple different tokens from 1 source address to 1 destination address * check expected token balances for both source and destination addresses for each token * check fees in Lovelace """ temp_template = helpers.get_func_name() amount = 1000 amount_lovelace = 10 rand = clusterlib.get_rand_str(5) new_tokens = clusterlib_utils.new_tokens( *[f"couttscoin{rand}{i}" for i in range(5)], cluster_obj=cluster, temp_template=f"{temp_template}_{rand}", token_mint_addr=payment_addrs[0], issuer_addr=payment_addrs[1], amount=1_000_000, ) new_tokens.append(new_token) src_address = new_token.token_mint_addr.address dst_address = payment_addrs[2].address src_init_balance = cluster.get_address_balance(src_address) src_init_balance_tokens = [ cluster.get_address_balance(src_address, coin=t.token) for t in new_tokens ] dst_init_balance_tokens = [ cluster.get_address_balance(dst_address, coin=t.token) for t in new_tokens ] destinations = [ clusterlib.TxOut(address=dst_address, amount=amount, coin=t.token) for t in new_tokens ] destinations.append( clusterlib.TxOut(address=dst_address, amount=amount_lovelace)) tx_files = clusterlib.TxFiles(signing_key_files={ t.token_mint_addr.skey_file for t in new_tokens }) tx_raw_output = cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, ) cluster.wait_for_new_block(new_blocks=2) assert ( cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - amount_lovelace ), f"Incorrect Lovelace balance for source address `{src_address}`" for idx, token in enumerate(new_tokens): assert ( cluster.get_address_balance( src_address, coin=token.token) == src_init_balance_tokens[idx] - amount ), f"Incorrect token #{idx} balance for source address `{src_address}`" assert ( cluster.get_address_balance( dst_address, coin=token.token) == dst_init_balance_tokens[idx] + amount ), f"Incorrect token #{idx} balance for destination address `{dst_address}`" @allure.link(helpers.get_vcs_link()) def test_transfer_no_ada( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], new_token: clusterlib_utils.TokenRecord, ): """Try to create an UTxO with just native tokens, no ADA. Expect failure.""" temp_template = helpers.get_func_name() amount = 10 src_address = new_token.token_mint_addr.address dst_address = payment_addrs[2].address destinations = [ clusterlib.TxOut(address=dst_address, amount=amount, coin=new_token.token) ] tx_files = clusterlib.TxFiles( signing_key_files=[new_token.token_mint_addr.skey_file]) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, ) assert "OutputTooSmallUTxO" in str(excinfo.value)
class TestDelegateAddr: """Tests for address delegation to stake pools.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) @pytest.mark.dbsync @pytest.mark.smoke def test_delegate_using_pool_id( self, cluster_manager: cluster_management.ClusterManager, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], use_build_cmd: bool, ): """Submit registration certificate and delegate to pool using pool id. * register stake address and delegate it to pool * check that the stake address was delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, use_build_cmd=use_build_cmd, ) tx_db_record = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_record, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) @pytest.mark.dbsync @pytest.mark.smoke @pytest.mark.skipif( cluster_nodes.get_cluster_type().type == cluster_nodes.ClusterType.TESTNET_NOPOOLS, reason="supposed to run on cluster with pools", ) def test_delegate_using_vkey( self, cluster_manager: cluster_management.ClusterManager, cluster_use_pool1: clusterlib.ClusterLib, use_build_cmd: bool, ): """Submit registration certificate and delegate to pool using cold vkey. * register stake address and delegate it to pool * check that the stake address was delegated * (optional) check records in db-sync """ pool_name = cluster_management.Resources.POOL1 cluster = cluster_use_pool1 temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool node_cold = cluster_manager.cache.addrs_data[pool_name][ "cold_key_pair"] delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, cold_vkey=node_cold.vkey_file, use_build_cmd=use_build_cmd, ) tx_db_record = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_record, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) @allure.link(helpers.get_vcs_link()) @pytest.mark.order(7) @pytest.mark.dbsync @pytest.mark.long def test_deregister( self, cluster_manager: cluster_management.ClusterManager, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], ): """Deregister stake address. * create two payment addresses that share single stake address * register and delegate the stake address to pool * attempt to deregister the stake address - deregistration is expected to fail because there are rewards in the stake address * withdraw rewards to payment address and deregister stake address * check that the key deposit was returned and rewards withdrawn * check that the stake address is no longer delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) # create two payment addresses that share single stake address (just to test that # delegation works as expected even under such circumstances) stake_addr_rec = clusterlib_utils.create_stake_addr_records( f"{temp_template}_addr0", cluster_obj=cluster)[0] payment_addr_recs = clusterlib_utils.create_payment_addr_records( f"{temp_template}_addr0", f"{temp_template}_addr1", cluster_obj=cluster, stake_vkey_file=stake_addr_rec.vkey_file, ) # fund payment address clusterlib_utils.fund_from_faucet( *payment_addr_recs, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) pool_user = clusterlib.PoolUser(payment=payment_addr_recs[1], stake=stake_addr_rec) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_user=pool_user, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" tx_db_deleg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) if tx_db_deleg: # check in db-sync that both payment addresses share single stake address assert (dbsync_utils.get_utxo( address=payment_addr_recs[0].address).stake_address == stake_addr_rec.address) assert (dbsync_utils.get_utxo( address=payment_addr_recs[1].address).stake_address == stake_addr_rec.address) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_deleg, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) src_address = delegation_out.pool_user.payment.address LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_id}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # files for deregistering stake address stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ delegation_out.pool_user.payment.skey_file, delegation_out.pool_user.stake.skey_file, ], ) # attempt to deregister the stake address - deregistration is expected to fail # because there are rewards in the stake address with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_dereg_fail", tx_files=tx_files_deregister, ) assert "StakeKeyNonZeroAccountBalanceDELEG" in str(excinfo.value) src_payment_balance = cluster.get_address_balance(src_address) reward_balance = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance # withdraw rewards to payment address, deregister stake address tx_raw_deregister_output = cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_dereg_withdraw", tx_files=tx_files_deregister, withdrawals=[ clusterlib.TxOut( address=delegation_out.pool_user.stake.address, amount=-1) ], ) # check that the key deposit was returned and rewards withdrawn assert ( cluster.get_address_balance(src_address) == src_payment_balance - tx_raw_deregister_output.fee + reward_balance + cluster.get_address_deposit() ), f"Incorrect balance for source address `{src_address}`" # check that the stake address is no longer delegated stake_addr_info = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address) assert (not stake_addr_info.delegation ), f"Stake address is still delegated: {stake_addr_info}" tx_db_dereg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=tx_raw_deregister_output) if tx_db_dereg: assert delegation_out.pool_user.stake.address in tx_db_dereg.stake_deregistration assert ( cluster.get_address_balance(src_address) == dbsync_utils.get_utxo(address=src_address).amount_sum ), f"Unexpected balance for source address `{src_address}` in db-sync" @allure.link(helpers.get_vcs_link()) @pytest.mark.order(7) @pytest.mark.dbsync @pytest.mark.long def test_undelegate( self, cluster_manager: cluster_management.ClusterManager, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], ): """Undelegate stake address. * submit registration certificate and delegate to pool * wait for first reward * undelegate stake address: - withdraw rewards to payment address - deregister stake address - re-register stake address * check that the key deposit was not returned * check that rewards were withdrawn * check that the stake address is still registered * check that the stake address is no longer delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" # check records in db-sync tx_db_deleg = dbsync_utils.check_tx( cluster_obj=cluster, tx_raw_output=delegation_out.tx_raw_output) delegation.db_check_delegation( pool_user=delegation_out.pool_user, db_record=tx_db_deleg, deleg_epoch=init_epoch, pool_id=delegation_out.pool_id, ) src_address = delegation_out.pool_user.payment.address LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_id}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # files for deregistering / re-registering stake address stake_addr_dereg_cert_file = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_undeleg_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_undeleg_addr0", stake_vkey_file=delegation_out.pool_user.stake.vkey_file, ) tx_files_undeleg = clusterlib.TxFiles( certificate_files=[ stake_addr_dereg_cert_file, stake_addr_reg_cert_file ], signing_key_files=[ delegation_out.pool_user.payment.skey_file, delegation_out.pool_user.stake.skey_file, ], ) src_payment_balance = cluster.get_address_balance(src_address) reward_balance = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance # withdraw rewards to payment address; deregister and re-register stake address tx_raw_undeleg = cluster.send_tx( src_address=src_address, tx_name=f"{temp_template}_undeleg_withdraw", tx_files=tx_files_undeleg, withdrawals=[ clusterlib.TxOut( address=delegation_out.pool_user.stake.address, amount=-1) ], ) # check that the key deposit was NOT returned and rewards were withdrawn assert ( cluster.get_address_balance(src_address) == src_payment_balance - tx_raw_undeleg.fee + reward_balance ), f"Incorrect balance for source address `{src_address}`" # check that the stake address is no longer delegated stake_addr_info = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address) assert stake_addr_info.address, f"Reward address is not registered: {stake_addr_info}" assert (not stake_addr_info.delegation ), f"Stake address is still delegated: {stake_addr_info}" this_epoch = cluster.wait_for_new_epoch(padding_seconds=20) assert cluster.get_stake_addr_info( delegation_out.pool_user.stake.address ).reward_account_balance, "No reward was received next epoch after undelegation" # check `transaction view` command tx_view.check_tx_view(cluster_obj=cluster, tx_raw_output=tx_raw_undeleg) # check records in db-sync tx_db_undeleg = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_undeleg) if tx_db_undeleg: assert delegation_out.pool_user.stake.address in tx_db_undeleg.stake_deregistration assert delegation_out.pool_user.stake.address in tx_db_undeleg.stake_registration db_rewards = dbsync_utils.check_address_reward( address=delegation_out.pool_user.stake.address, epoch_from=init_epoch) assert db_rewards db_reward_epochs = sorted(r.spendable_epoch for r in db_rewards.rewards) assert db_reward_epochs[0] == init_epoch + 4 assert this_epoch in db_reward_epochs @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) @pytest.mark.dbsync @pytest.mark.smoke def test_addr_registration_deregistration( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Submit registration and deregistration certificates in single TX. * create stake address registration cert * create stake address deregistration cert * register and deregister stake address in single TX * check that the balance for source address was correctly updated and that key deposit was not needed * (optional) check records in db-sync """ temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment src_init_balance = cluster.get_address_balance(user_payment.address) # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # create stake address deregistration cert stake_addr_dereg_cert_file = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register and deregister stake address in single TX tx_files = clusterlib.TxFiles( certificate_files=[ stake_addr_reg_cert_file, stake_addr_dereg_cert_file, ] * 3, signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) if use_build_cmd: tx_raw_output = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_reg_deleg", tx_files=tx_files, fee_buffer=2_000_000, deposit=0, witness_override=len(tx_files.signing_key_files), ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_reg_deleg", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output.txins) else: tx_raw_output = cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_reg_dereg", tx_files=tx_files, deposit=0, ) # check that the balance for source address was correctly updated and that key deposit # was not needed assert ( cluster.get_address_balance( user_payment.address) == src_init_balance - tx_raw_output.fee ), f"Incorrect balance for source address `{user_payment.address}`" tx_db_record = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_output) if tx_db_record: assert user_registered.stake.address in tx_db_record.stake_registration assert user_registered.stake.address in tx_db_record.stake_deregistration @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) @pytest.mark.dbsync @pytest.mark.smoke def test_addr_delegation_deregistration( self, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Submit delegation and deregistration certificates in single TX. * create stake address registration cert * create stake address deregistration cert * register stake address * create stake address delegation cert * delegate and deregister stake address in single TX * check that the balance for source address was correctly updated and that the key deposit was returned * check that the stake address was NOT delegated * (optional) check records in db-sync """ cluster, pool_id = cluster_and_pool temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment src_init_balance = cluster.get_address_balance(user_payment.address) # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # create stake address deregistration cert stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register stake address tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_reg_cert_file], signing_key_files=[user_payment.skey_file], ) tx_raw_output_reg = cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_reg", tx_files=tx_files, ) tx_db_reg = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_output_reg) if tx_db_reg: assert user_registered.stake.address in tx_db_reg.stake_registration # check that the balance for source address was correctly updated assert ( cluster.get_address_balance( user_payment.address) == src_init_balance - tx_raw_output_reg.fee - cluster.get_address_deposit() ), f"Incorrect balance for source address `{user_payment.address}`" src_registered_balance = cluster.get_address_balance( user_payment.address) # create stake address delegation cert stake_addr_deleg_cert_file = cluster.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file, stake_pool_id=pool_id, ) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # delegate and deregister stake address in single TX tx_files = clusterlib.TxFiles( certificate_files=[ stake_addr_deleg_cert_file, stake_addr_dereg_cert ], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) if use_build_cmd: tx_raw_output_deleg = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_dereg", tx_files=tx_files, fee_buffer=2_000_000, witness_override=len(tx_files.signing_key_files), ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output_deleg.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_deleg_dereg", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output_deleg.txins) else: tx_raw_output_deleg = cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_dereg", tx_files=tx_files, ) # check that the balance for source address was correctly updated and that the key # deposit was returned assert ( cluster.get_address_balance( user_payment.address) == src_registered_balance - tx_raw_output_deleg.fee + cluster.get_address_deposit() ), f"Incorrect balance for source address `{user_payment.address}`" # check that the stake address was NOT delegated stake_addr_info = cluster.get_stake_addr_info( user_registered.stake.address) assert not stake_addr_info.delegation, f"Stake address was delegated: {stake_addr_info}" tx_db_deleg = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_output_deleg) if tx_db_deleg: assert user_registered.stake.address in tx_db_deleg.stake_deregistration assert user_registered.stake.address == tx_db_deleg.stake_delegation[ 0].address assert tx_db_deleg.stake_delegation[ 0].active_epoch_no == init_epoch + 2 assert pool_id == tx_db_deleg.stake_delegation[0].pool_id @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) @pytest.mark.dbsync @pytest.mark.smoke def test_addr_registration_certificate_order( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Submit (de)registration certificates in single TX and check that the order matter. * create stake address registration cert * create stake address deregistration cert * register, deregister, register, deregister and register stake address in single TX * check that the address is registered * check that the balance for source address was correctly updated and that key deposit was needed * (optional) check records in db-sync """ temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment src_init_balance = cluster.get_address_balance(user_payment.address) # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # create stake address deregistration cert stake_addr_dereg_cert_file = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register, deregister, register, deregister and register stake address in single TX # prove that the order matters tx_files = clusterlib.TxFiles( certificate_files=[ stake_addr_reg_cert_file, stake_addr_dereg_cert_file, stake_addr_reg_cert_file, stake_addr_dereg_cert_file, stake_addr_reg_cert_file, ], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) deposit = cluster.get_address_deposit() if use_build_cmd: tx_raw_output = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_reg_dereg_cert_order", tx_files=tx_files, fee_buffer=2_000_000, witness_override=len(tx_files.signing_key_files), deposit=deposit, ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_reg_dereg_cert_order", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output.txins) else: tx_raw_output = cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_reg_dereg", tx_files=tx_files, deposit=deposit, ) # check that the stake address is registered assert cluster.get_stake_addr_info( user_registered.stake.address).address # check that the balance for source address was correctly updated and that key deposit # was needed assert ( cluster.get_address_balance( user_payment.address) == src_init_balance - tx_raw_output.fee - deposit ), f"Incorrect balance for source address `{user_payment.address}`" tx_db_record = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_output) if tx_db_record: assert user_registered.stake.address in tx_db_record.stake_registration assert user_registered.stake.address in tx_db_record.stake_deregistration
class TestNegative: """Tests that are expected to fail.""" @allure.link(helpers.get_vcs_link()) def test_registration_cert_with_wrong_key( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], ): """Try to generate stake address registration certificate using wrong stake vkey. Expect failure. """ temp_template = common.get_test_id(cluster) # create stake address registration cert, use wrong stake vkey with pytest.raises(clusterlib.CLIError) as excinfo: cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=pool_users[0].payment.vkey_file) assert "Expected: StakeVerificationKeyShelley" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) def test_delegation_cert_with_wrong_key( self, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], pool_users: List[clusterlib.PoolUser], ): """Try to generate stake address delegation certificate using wrong stake vkey. Expect failure. """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) # create stake address delegation cert, use wrong stake vkey with pytest.raises(clusterlib.CLIError) as excinfo: cluster.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=pool_users[0].payment.vkey_file, stake_pool_id=pool_id, ) assert "Expected: StakeVerificationKeyShelley" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) def test_register_addr_with_wrong_key( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], ): """Try to register stake address using wrong payment skey. Expect failure. """ temp_template = common.get_test_id(cluster) user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register stake address, use wrong payment skey tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_reg_cert_file], signing_key_files=[pool_users[1].payment.skey_file], ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx(src_address=user_payment.address, tx_name=temp_template, tx_files=tx_files) assert "MissingVKeyWitnessesUTXOW" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) def test_delegate_addr_with_wrong_key( self, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], ): """Try to delegate stake address using wrong payment skey. Expect failure. """ cluster, pool_id = cluster_and_pool temp_template = common.get_test_id(cluster) user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register stake address tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_reg_cert_file], signing_key_files=[user_payment.skey_file], ) cluster.send_tx(src_address=user_payment.address, tx_name=f"{temp_template}_reg", tx_files=tx_files) # create stake address delegation cert stake_addr_deleg_cert_file = cluster.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file, stake_pool_id=pool_id, ) # delegate stake address, use wrong payment skey tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_deleg_cert_file], signing_key_files=[pool_users[1].payment.skey_file], ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg", tx_files=tx_files, ) assert "MissingVKeyWitnessesUTXOW" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) def test_delegate_unknown_addr( self, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Try to delegate unknown stake address. Expect failure. """ cluster, pool_id = cluster_and_pool temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment # create stake address delegation cert stake_addr_deleg_cert_file = cluster.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file, stake_pool_id=pool_id, ) # delegate unknown stake address tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_deleg_cert_file], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) with pytest.raises(clusterlib.CLIError) as excinfo: if use_build_cmd: tx_raw_output = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_unknown", tx_files=tx_files, fee_buffer=2_000_000, witness_override=len(tx_files.signing_key_files), ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_deleg_unknown", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output.txins) else: cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_unknown", tx_files=tx_files, ) assert "StakeDelegationImpossibleDELEG" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) def test_delegate_deregistered_addr( self, cluster_and_pool: Tuple[clusterlib.ClusterLib, str], pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Try to delegate deregistered stake address. Expect failure. """ cluster, pool_id = cluster_and_pool temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment # create stake address registration cert stake_addr_reg_cert_file = cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) # register stake address tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_reg_cert_file], signing_key_files=[user_payment.skey_file], ) cluster.send_tx(src_address=user_payment.address, tx_name=f"{temp_template}_reg", tx_files=tx_files) # deregister stake address stake_addr_dereg_cert_file = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file, ) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert_file], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file, ], ) cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_dereg", tx_files=tx_files_deregister, ) # create stake address delegation cert stake_addr_deleg_cert_file = cluster.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file, stake_pool_id=pool_id, ) # delegate deregistered stake address tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_deleg_cert_file], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) with pytest.raises(clusterlib.CLIError) as excinfo: if use_build_cmd: tx_raw_output = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_dereg", tx_files=tx_files, fee_buffer=2_000_000, witness_override=len(tx_files.signing_key_files), ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_deleg_dereg", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output.txins) else: cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_deleg_dereg", tx_files=tx_files, ) assert "StakeDelegationImpossibleDELEG" in str(excinfo.value) @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize( "use_build_cmd", ( False, pytest.param( True, marks=pytest.mark.skipif(not common.BUILD_USABLE, reason=common.BUILD_SKIP_MSG), ), ), ids=("build_raw", "build"), ) def test_deregister_not_registered_addr( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], pool_users_disposable: List[clusterlib.PoolUser], use_build_cmd: bool, ): """Deregister not registered stake address.""" temp_template = f"{common.get_test_id(cluster)}_{use_build_cmd}" user_registered = pool_users_disposable[0] user_payment = pool_users[0].payment # files for deregistering stake address stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=user_registered.stake.vkey_file) tx_files = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ user_payment.skey_file, user_registered.stake.skey_file ], ) with pytest.raises(clusterlib.CLIError) as excinfo: if use_build_cmd: tx_raw_output = cluster.build_tx( src_address=user_payment.address, tx_name=f"{temp_template}_dereg_fail", tx_files=tx_files, fee_buffer=2_000_000, witness_override=len(tx_files.signing_key_files), ) tx_signed = cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_dereg_fail", ) cluster.submit_tx(tx_file=tx_signed, txins=tx_raw_output.txins) else: cluster.send_tx( src_address=user_payment.address, tx_name=f"{temp_template}_dereg_fail", tx_files=tx_files, ) assert "StakeKeyNotRegisteredDELEG" in str(excinfo.value)
class TestAddressInfo: """Tests for cardano-cli address info.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize("addr_gen", ("static", "dynamic")) def test_address_info_payment(self, cluster: clusterlib.ClusterLib, addr_gen: str): """Check payment address info.""" if addr_gen == "static": address = "addr_test1vzp4kj0rmnl5q5046e2yy697fndej56tm35jekemj6ew2gczp74wk" else: payment_rec = cluster.gen_payment_addr_and_keys( name=helpers.get_func_name(), ) address = payment_rec.address addr_info = cluster.address_info(address=address) assert addr_info.address == address assert addr_info.era == "shelley" assert addr_info.encoding == "bech32" assert addr_info.type == "payment" if addr_gen == "static": assert addr_info.base16 == "60835b49e3dcff4051f5d6544268be4cdb99534bdc692cdb3b96b2e523" @allure.link(helpers.get_vcs_link()) @pytest.mark.parametrize("addr_gen", ("static", "dynamic")) def test_address_info_stake(self, cluster: clusterlib.ClusterLib, addr_gen: str): """Check stake address info.""" if addr_gen == "static": address = "stake_test1uz5mstpskyhpcvaw2enlfk8fa5k335cpd0lfz6chd5c2xpck3nld4" else: stake_rec = cluster.gen_stake_addr_and_keys( name=helpers.get_func_name(), ) address = stake_rec.address addr_info = cluster.address_info(address=address) assert addr_info.address == address assert addr_info.era == "shelley" assert addr_info.encoding == "bech32" assert addr_info.type == "stake" if addr_gen == "static": assert addr_info.base16 == "e0a9b82c30b12e1c33ae5667f4d8e9ed2d18d3016bfe916b176d30a307" @allure.link(helpers.get_vcs_link()) def test_address_info_script(self, cluster: clusterlib.ClusterLib): """Check script address info.""" temp_template = helpers.get_func_name() # create payment address payment_rec = cluster.gen_payment_addr_and_keys( name=temp_template, ) # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=[payment_rec.vkey_file], slot=100, slot_type_arg=clusterlib.MultiSlotTypeArgs.AFTER, ) # create script address address = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) addr_info = cluster.address_info(address=address) assert addr_info.address == address assert addr_info.era == "shelley" assert addr_info.encoding == "bech32" assert addr_info.type == "payment"
class TestLobsterChallenge: @allure.link(helpers.get_vcs_link()) @pytest.mark.dbsync @pytest.mark.testnets def test_lobster_name(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Test the Lobster Challenge. Uses `cardano-cli transaction build` command for building the transactions. * fund token issuer and create a UTxO for collateral * mint the LobsterNFT token * deploy the LobsterNFT token to address of lobster spending script * generate random votes and determine the expected final value * perform voting and check that the final value matches the expected value * (optional) check transactions in db-sync """ # pylint: disable=too-many-locals,too-many-statements temp_template = common.get_test_id(cluster) payment_addr = payment_addrs[0] issuer_addr = payment_addrs[1] votes_num = 50 names_num = 1219 io_random_seed = 42 issuer_fund = 200_000_000 lovelace_setup_amount = 1_724_100 lovelace_vote_amount = 2_034_438 collateral_amount = 20_000_000 nft_amount = 1 # Step 1: fund the token issuer and create UTXO for collaterals mint_utxos, collateral_utxos, tx_output_step1 = _fund_issuer( cluster_obj=cluster, temp_template=temp_template, payment_addr=payment_addr, issuer_addr=issuer_addr, amount=issuer_fund, collateral_amount=collateral_amount, ) # Step 2: mint the LobsterNFT token lobster_nft_token, token_utxos_step2, tx_output_step2 = _mint_lobster_nft( cluster_obj=cluster, temp_template=temp_template, issuer_addr=issuer_addr, mint_utxos=mint_utxos, collateral_utxos=collateral_utxos, nft_amount=nft_amount, lovelace_amount=lovelace_setup_amount, ) # Step 3: deploy the LobsterNFT token to script address script_address, token_utxos_step3, tx_output_step3 = _deploy_lobster_nft( cluster_obj=cluster, temp_template=temp_template, issuer_addr=issuer_addr, token_utxos=token_utxos_step2, lobster_nft_token=lobster_nft_token, nft_amount=nft_amount, lovelace_amount=lovelace_setup_amount, ) tx_outputs_all = [tx_output_step1, tx_output_step2, tx_output_step3] # Step 4: prepare for voting # there's 50 votes, each vote is int between 1 and 100 votes = [random.randint(1, 100) for __ in range(votes_num)] _votes_sum = sum(votes) # Add "random" seed to the sum of all votes. Taking the remainder after # division by the number of potential names (`names_num`) gives us the # final counter value. # The final counter value is used as an index. Looking into the list of # names, we can see the name the index points to. We don't need to do # that in automated test, we will just check that the final counter # value matches the expected counter value. expected_counter_val = (io_random_seed + _votes_sum) % names_num votes.append(expected_counter_val) # Step 5: vote other_policyid = cluster.get_policyid(OTHER_MINT_PLUTUS) asset_name_counter = b"LobsterCounter".hex() asset_name_votes = b"LobsterVotes".hex() counter_token = f"{other_policyid}.{asset_name_counter}" votes_token = f"{other_policyid}.{asset_name_votes}" vote_utxos = token_utxos_step3 vote_counter = 0 utxo_votes_token: Optional[clusterlib.UTXOData] = None utxo_counter_token: Optional[clusterlib.UTXOData] = None for vote_num, vote_val in enumerate(votes, start=1): # normal votes if vote_num <= votes_num: vote_counter += vote_val mint_val = vote_val # final IO vote else: # set new counter value to `(seed + counter value) % number of names` # and burn excesive LobsterCounter tokens mint_val = vote_val - vote_counter vote_counter = vote_val txouts = [ # Lovelace amount clusterlib.TxOut( address=script_address, amount=lovelace_vote_amount, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterNFT token clusterlib.TxOut( address=script_address, amount=nft_amount, coin=lobster_nft_token, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterCounter token clusterlib.TxOut( address=script_address, amount=vote_counter, coin=counter_token, datum_hash=LOBSTER_DATUM_HASH, ), # LobsterVotes token clusterlib.TxOut( address=script_address, amount=vote_num, coin=votes_token, datum_hash=LOBSTER_DATUM_HASH, ), ] mint_txouts = [ # mint new LobsterCounter tokens clusterlib.TxOut( address=script_address, amount=mint_val, coin=counter_token, datum_hash=LOBSTER_DATUM_HASH, ), # mint 1 new LobsterVotes token clusterlib.TxOut( address=script_address, amount=1, coin=votes_token, datum_hash=LOBSTER_DATUM_HASH, ), ] mint_script_data = [ clusterlib.Mint( txouts=mint_txouts, script_file=OTHER_MINT_PLUTUS, redeemer_value="[]", ) ] txin_script_data = [ clusterlib.ScriptTxIn( txins=vote_utxos, script_file=LOBSTER_PLUTUS, collaterals=collateral_utxos, datum_value="[]", redeemer_value="[]", ) ] tx_files = clusterlib.TxFiles(signing_key_files=[ payment_addr.skey_file, issuer_addr.skey_file ], ) funds_txin = cluster.get_utxo_with_highest_amount( address=payment_addr.address) tx_output_vote = cluster.build_tx( src_address=payment_addr.address, tx_name=f"{temp_template}_voting_{vote_num}", txins=[funds_txin], tx_files=tx_files, txouts=txouts, script_txins=txin_script_data, mint=mint_script_data, ) tx_signed = cluster.sign_tx( tx_body_file=tx_output_vote.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_voting_{vote_num}", ) cluster.submit_tx(tx_file=tx_signed, txins=vote_utxos) tx_outputs_all.append(tx_output_vote) txid_vote = cluster.get_txid(tx_body_file=tx_output_vote.out_file) vote_utxos = cluster.get_utxo(txin=f"{txid_vote}#1") # check expected balances utxos_lovelace = [ u for u in vote_utxos if u.coin == clusterlib.DEFAULT_COIN ][0] assert ( utxos_lovelace.amount == lovelace_vote_amount ), f"Incorrect Lovelace balance for script address `{script_address}`" utxo_votes_token = [ u for u in vote_utxos if u.coin == votes_token ][0] assert ( utxo_votes_token.amount == vote_num ), f"Incorrect LobsterVotes token balance for script address `{script_address}`" utxo_counter_token = [ u for u in vote_utxos if u.coin == counter_token ][0] assert ( utxo_counter_token.amount == vote_counter ), f"Incorrect LobsterCounter token balance for script address `{script_address}`" assert ( utxo_counter_token and utxo_counter_token.amount == expected_counter_val ), "Final balance of LobsterCounter token doesn't match the expected balance" # check transactions in db-sync for tx_out_rec in tx_outputs_all: dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_out_rec)
class TestPolicies: """Tests for minting and burning tokens using minting policies.""" @allure.link(helpers.get_vcs_link()) def test_valid_policy_after(self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test minting and burning tokens after given slot.""" expected_fee = 351093 temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=100, slot_type_arg=clusterlib.MultiSlotTypeArgs.AFTER, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting tx_out_mint = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=100, invalid_hereafter=cluster.get_slot_no() + 1000, ) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint] tx_out_burn = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_burn, temp_template=f"{temp_template}_burn", invalid_before=100, invalid_hereafter=cluster.get_slot_no() + 1000, ) for t in tokens_to_burn: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @allure.link(helpers.get_vcs_link()) def test_valid_policy_before( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test minting and burning tokens before given slot.""" expected_fee = 351093 temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] before_slot = cluster.get_slot_no() + 10_000 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=before_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting tx_out_mint = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=100, invalid_hereafter=cluster.get_slot_no() + 1000, ) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert token_utxo and token_utxo[ 0].amount == amount, "The token was not minted" # token burning tokens_to_burn = [t._replace(amount=-amount) for t in tokens_to_mint] tx_out_burn = _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_burn, temp_template=f"{temp_template}_burn", invalid_before=100, invalid_hereafter=cluster.get_slot_no() + 1000, ) for t in tokens_to_burn: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was not burnt" # check expected fees assert helpers.is_in_interval( tx_out_mint.fee, expected_fee, frac=0.15) and helpers.is_in_interval( tx_out_burn.fee, expected_fee, frac=0.15), "TX fee doesn't fit the expected interval" @allure.link(helpers.get_vcs_link()) def test_policy_before_past(self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test that it's NOT possible to mint tokens when the "before" slot is in the past.""" temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] before_slot = cluster.get_slot_no() - 1 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=before_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting - valid range, slot is already in the past with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=1, invalid_hereafter=before_slot, ) assert "OutsideValidityIntervalUTxO" in str(excinfo.value) # token minting - invalid range, slot is already in the past with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=1, invalid_hereafter=before_slot + 1, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was minted unexpectedly" @allure.link(helpers.get_vcs_link()) def test_policy_before_future( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test that it's NOT possible to mint tokens. The "before" slot is in the future and the given range is invalid. """ temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] before_slot = cluster.get_slot_no() + 10_000 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=before_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting - invalid range, slot is in the future with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=1, invalid_hereafter=before_slot + 1, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was minted unexpectedly" @allure.link(helpers.get_vcs_link()) def test_policy_after_future( self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test that it's NOT possible to mint tokens. The "after" slot is in the future and the given range is invalid. """ temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] after_slot = cluster.get_slot_no() + 10_000 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=after_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.AFTER, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting - valid range, slot is in the future with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=after_slot, invalid_hereafter=after_slot + 100, ) assert "OutsideValidityIntervalUTxO" in str(excinfo.value) # token minting - invalid range, slot is in the future with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=1, invalid_hereafter=after_slot, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was minted unexpectedly" @allure.link(helpers.get_vcs_link()) def test_policy_after_past(self, cluster: clusterlib.ClusterLib, issuers_addrs: List[clusterlib.AddressRecord]): """Test that it's NOT possible to mint tokens. The "after" slot is in the past. """ temp_template = helpers.get_func_name() rand = clusterlib.get_rand_str(4) amount = 5 token_mint_addr = issuers_addrs[0] payment_vkey_files = [p.vkey_file for p in issuers_addrs] after_slot = cluster.get_slot_no() - 1 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=payment_vkey_files[1:], slot=after_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.AFTER, ) policyid = cluster.get_policyid(multisig_script) tokens_to_mint = [] for tnum in range(5): asset_name = f"couttscoin{rand}{tnum}" token = f"{policyid}.{asset_name}" assert not cluster.get_utxo(token_mint_addr.address, coins=[ token ]), "The token already exists" tokens_to_mint.append( clusterlib_utils.TokenRecord( token=token, asset_name=asset_name, amount=amount, issuers_addrs=issuers_addrs, token_mint_addr=token_mint_addr, script=multisig_script, )) # token minting - valid slot, invalid range - `invalid_hereafter` is in the past with pytest.raises(clusterlib.CLIError) as excinfo: _mint_or_burn_witness( cluster_obj=cluster, new_tokens=tokens_to_mint, temp_template=f"{temp_template}_mint", invalid_before=1, invalid_hereafter=after_slot, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value) for t in tokens_to_mint: token_utxo = cluster.get_utxo(token_mint_addr.address, coins=[t.token]) assert not token_utxo, "The token was minted unexpectedly"
class TestLockingV2: """Tests for Tx output locking using Plutus V2 smart contracts.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.testnets def test_reference_locking( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], ): """Test locking a Tx output with a Plutus script and spending the locked UTxO. * create a Tx output with an inline datum at the script address * create a Tx output with a reference script * check that the expected amount was locked at the script address * spend the locked UTxO using the reference UTxO * check that the expected amount was spent * check that the reference UTxO was not spent """ temp_template = common.get_test_id(cluster) amount = 2_000_000 protocol_params = cluster.get_protocol_params() plutus_op = plutus_common.PlutusOp( script_file=plutus_common.ALWAYS_SUCCEEDS_PLUTUS_V2, datum_file=plutus_common.DATUM_42_TYPED, redeemer_cbor_file=plutus_common.REDEEMER_42_CBOR, execution_cost=plutus_common.ALWAYS_SUCCEEDS_V2_COST, ) # Step 1: fund the Plutus script # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file script_address = cluster.gen_payment_addr( addr_name=f"{temp_template}_addr", payment_script_file=plutus_op.script_file) redeem_cost = plutus_common.compute_cost( execution_cost=plutus_op.execution_cost, protocol_params=protocol_params) # create a Tx output with a datum hash at the script address tx_files_fund = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], ) txouts_fund = [ clusterlib.TxOut( address=script_address, amount=amount + redeem_cost.fee + FEE_REDEEM_TXSIZE, inline_datum_file=plutus_op.datum_file, ), # for reference script clusterlib.TxOut( address=payment_addrs[1].address, amount=2_000_000, reference_script_file=plutus_op.script_file, ), # for collateral clusterlib.TxOut(address=payment_addrs[1].address, amount=redeem_cost.collateral), ] tx_output_fund = cluster.send_tx( src_address=payment_addrs[0].address, tx_name=f"{temp_template}_step1", txouts=txouts_fund, tx_files=tx_files_fund, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=2, join_txouts=False, ) txid_fund = cluster.get_txid(tx_body_file=tx_output_fund.out_file) script_utxos = cluster.get_utxo(txin=f"{txid_fund}#0", coins=[clusterlib.DEFAULT_COIN]) reference_utxos = cluster.get_utxo(txin=f"{txid_fund}#1") collateral_utxos = cluster.get_utxo(txin=f"{txid_fund}#2") assert script_utxos, "No script UTxO" assert reference_utxos, "No reference UTxO" assert collateral_utxos, "No collateral UTxO" assert ( script_utxos[0].amount == amount + redeem_cost.fee + FEE_REDEEM_TXSIZE ), f"Incorrect balance for script address `{script_utxos[0].address}`" assert (script_utxos[0].inline_datum_hash and script_utxos[0].inline_datum ), "Inline datum not present on script UTxO" assert reference_utxos[ 0].reference_script, "Reference script not present on reference UTxO" # Step 2: spend the "locked" UTxO # for mypy assert plutus_op.execution_cost assert plutus_op.datum_file assert plutus_op.redeemer_cbor_file reference_utxo = reference_utxos[0] plutus_txins = [ clusterlib.ScriptTxIn( txins=script_utxos, reference_txin=reference_utxo, reference_type=clusterlib.ScriptTypes.PLUTUS_V2, collaterals=collateral_utxos, execution_units=( plutus_op.execution_cost.per_time, plutus_op.execution_cost.per_space, ), inline_datum_present=True, redeemer_cbor_file=plutus_op.redeemer_cbor_file, ), ] tx_files_redeem = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file], ) txouts_redeem = [ clusterlib.TxOut(address=payment_addrs[1].address, amount=amount), ] tx_output_redeem = cluster.build_raw_tx_bare( out_file=f"{temp_template}_step2_tx.body", txouts=txouts_redeem, tx_files=tx_files_redeem, fee=redeem_cost.fee + FEE_REDEEM_TXSIZE, script_txins=plutus_txins, ) tx_signed_redeem = cluster.sign_tx( tx_body_file=tx_output_redeem.out_file, signing_key_files=tx_files_redeem.signing_key_files, tx_name=f"{temp_template}_step2", ) dst_init_balance = cluster.get_address_balance( payment_addrs[1].address) cluster.submit_tx( tx_file=tx_signed_redeem, txins=[ t.txins[0] for t in tx_output_redeem.script_txins if t.txins ], ) assert ( cluster.get_address_balance( payment_addrs[1].address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{payment_addrs[1].address}`" script_utxos_lovelace = [ u for u in script_utxos if u.coin == clusterlib.DEFAULT_COIN ] for u in script_utxos_lovelace: assert not cluster.get_utxo( txin=f"{u.utxo_hash}#{u.utxo_ix}", coins=[clusterlib.DEFAULT_COIN ]), f"Inputs were NOT spent for `{u.address}`" assert cluster.get_utxo( txin=f"{reference_utxo.utxo_hash}#{reference_utxo.utxo_ix}", coins=[clusterlib.DEFAULT_COIN], ), "Reference input was spent"
class TestNoRewards: @allure.link(helpers.get_vcs_link()) def test_no_reward_unmet_pledge1( self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Check that the stake pool is not receiving rewards when pledge is not met. When the pledge is higher than available funds, neither pool owners nor those who delegate to that pool receive rewards. * delegate stake address * wait for first reward * increase the needed pledge amount - update the pool parameters by resubmitting the pool registration certificate - the funds are now lower than what is needed by the stake pool * check that NO new rewards were received by those delegating to the pool * check that pool owner is also NOT receiving rewards * return the pool to the original state - restore pledge settings * check that new rewards were received by those delegating to the pool * check that pool owner is also receiving rewards """ pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_owner = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["stake"]) temp_template = common.get_test_id(cluster) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=pool_name) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish the pool update in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # load and update original pool data loaded_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=f"changed_{pool_name}", pool_id=pool_id) pool_data_updated = loaded_data._replace( pool_pledge=loaded_data.pool_pledge * 9) # increase the needed pledge amount - update the pool parameters by resubmitting the pool # registration certificate cluster.register_stake_pool( pool_data=pool_data_updated, pool_owners=[pool_owner], vrf_vkey_file=pool_rec["vrf_key_pair"].vkey_file, cold_key_pair=pool_rec["cold_key_pair"], tx_name=f"{temp_template}_update_param", reward_account_vkey_file=pool_rec["reward"].vkey_file, deposit=0, # no additional deposit, the pool is already registered ) cluster.wait_for_new_epoch(4, padding_seconds=30) orig_owner_reward = cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance orig_user_reward = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance cluster.wait_for_new_epoch(3) with cluster_manager.restart_on_failure(): # check that NO new rewards were received by those delegating to the pool assert (orig_user_reward == cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "Received unexpected rewards" # check that pool owner is also NOT receiving rewards assert (orig_owner_reward == cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "Pool owner received unexpected rewards" # Return the pool to the original state - restore pledge settings. # fund pool owner's addresses so balance keeps higher than pool pledge after fees etc. # are deducted clusterlib_utils.fund_from_faucet( pool_owner, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) # update the pool to original parameters by resubmitting # the pool registration certificate cluster.register_stake_pool( pool_data=loaded_data, pool_owners=[pool_owner], vrf_vkey_file=pool_rec["vrf_key_pair"].vkey_file, cold_key_pair=pool_rec["cold_key_pair"], tx_name=f"{temp_template}_update_to_orig", reward_account_vkey_file=pool_rec["reward"].vkey_file, deposit= 0, # no additional deposit, the pool is already registered ) cluster.wait_for_new_epoch(5, padding_seconds=30) # check that new rewards were received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # check that pool owner is also receiving rewards assert (orig_owner_reward < cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "New reward was not received by pool reward address" # check that pledge is still met after the owner address was used to pay for Txs pool_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=pool_name, pool_id=pool_id) owner_payment_balance = cluster.get_address_balance( pool_owner.payment.address) assert (owner_payment_balance >= pool_data.pool_pledge ), f"Pledge is not met for pool '{pool_name}'!" @allure.link(helpers.get_vcs_link()) def test_no_reward_unmet_pledge2( self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Check that the stake pool is not receiving rewards when pledge is not met. When the pledge is higher than available funds, neither pool owners nor those who delegate to that pool receive rewards. * delegate stake address * wait for first reward * withdraw part of the pledge - the funds are lower than what is needed by the stake pool * check that NO new rewards were received by those delegating to the pool * check that pool owner is also NOT receiving rewards * return the pool to the original state - restore pledge funds * check that new rewards were received by those delegating to the pool * check that pool owner is also receiving rewards """ pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_owner = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["stake"]) temp_template = common.get_test_id(cluster) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=pool_name) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to withdraw the pledge in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # load pool data loaded_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=f"changed_{pool_name}", pool_id=pool_id) pledge_amount = loaded_data.pool_pledge // 2 # withdraw part of the pledge destinations = [ clusterlib.TxOut(address=delegation_out.pool_user.payment.address, amount=pledge_amount) ] tx_files = clusterlib.TxFiles( signing_key_files=[pool_owner.payment.skey_file]) cluster.send_funds( src_address=pool_owner.payment.address, destinations=destinations, tx_name=f"{temp_template}_withdraw_pledge", tx_files=tx_files, ) assert cluster.get_address_balance( pool_owner.payment.address ) < loaded_data.pool_pledge, ( f"Pledge still high - pledge: {loaded_data.pool_pledge}, " f"funds: {cluster.get_address_balance(pool_owner.payment.address)}" ) cluster.wait_for_new_epoch(4, padding_seconds=30) orig_owner_reward = cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance orig_user_reward = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance cluster.wait_for_new_epoch(3) with cluster_manager.restart_on_failure(): # check that NO new rewards were received by those delegating to the pool assert (orig_user_reward == cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "Received unexpected rewards" # check that pool owner is also NOT receiving rewards assert (orig_owner_reward == cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "Pool owner received unexpected rewards" # Return the pool to the original state - restore pledge funds. # fund user address so it has enough funds for fees etc. clusterlib_utils.fund_from_faucet( delegation_out.pool_user, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) # return pledge destinations = [ clusterlib.TxOut(address=pool_owner.payment.address, amount=pledge_amount + 100_000_000) ] tx_files = clusterlib.TxFiles( signing_key_files=[delegation_out.pool_user.payment.skey_file]) cluster.send_funds( src_address=delegation_out.pool_user.payment.address, destinations=destinations, tx_name=f"{temp_template}_return_pledge", tx_files=tx_files, ) assert ( cluster.get_address_balance( pool_owner.payment.address) >= loaded_data.pool_pledge ), (f"Funds still low - pledge: {loaded_data.pool_pledge}, " f"funds: {cluster.get_address_balance(pool_owner.payment.address)}" ) cluster.wait_for_new_epoch(5, padding_seconds=30) # check that new rewards were received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # check that pool owner is also receiving rewards assert (orig_owner_reward < cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "New reward was not received by pool reward address" @allure.link(helpers.get_vcs_link()) def test_no_reward_deregistered_stake_addr( self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Check that the pool is not receiving rewards when owner's stake address is deregistered. When the owner's stake address is deregistered (i.e. owner's stake is lower than pledge), neither pool owners nor those who delegate to that pool receive rewards. * delegate stake address * wait for first reward * deregister stake address - owner's stake is lower than pledge * check that the key deposit was returned * check that NO new rewards were received by those delegating to the pool * check that pool owner is also NOT receiving rewards * return the pool to the original state - reregister stake address and delegate it to the pool * check that new rewards were received by those delegating to the pool * check that pool owner is also receiving rewards """ pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_owner = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["stake"]) temp_template = common.get_test_id(cluster) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=pool_name) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # deregister stake address - owner's stake is lower than pledge stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=pool_owner.stake.vkey_file) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ pool_owner.payment.skey_file, pool_owner.stake.skey_file ], ) src_init_balance = cluster.get_address_balance( pool_owner.payment.address) tx_raw_deregister_output = cluster.send_tx( src_address=pool_owner.payment.address, tx_name=f"{temp_template}_dereg", tx_files=tx_files_deregister, ) with cluster_manager.restart_on_failure(): # check that the key deposit was returned assert ( cluster.get_address_balance( pool_owner.payment.address) == src_init_balance - tx_raw_deregister_output.fee + cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_owner.payment.address}`" # check that the stake address is no longer delegated assert not cluster.get_stake_addr_info( pool_owner.stake.address), "Stake address still delegated" cluster.wait_for_new_epoch(4, padding_seconds=30) orig_owner_reward = cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance orig_user_reward = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance cluster.wait_for_new_epoch(3) # check that NO new rewards were received by those delegating to the pool assert (orig_user_reward == cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "Received unexpected rewards" # check that pool owner is also NOT receiving rewards assert (orig_owner_reward == cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "Pool owner received unexpected rewards" # Return the pool to the original state - reregister stake address and # delegate it to the pool. # fund pool owner's addresses so balance keeps higher than pool pledge after fees etc. # are deducted clusterlib_utils.fund_from_faucet( pool_owner, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) src_updated_balance = cluster.get_address_balance( pool_owner.payment.address) # reregister stake address and delegate it to pool tx_files = clusterlib.TxFiles( certificate_files=[ pool_rec["stake_addr_registration_cert"], pool_rec["stake_addr_delegation_cert"], ], signing_key_files=[ pool_owner.payment.skey_file, pool_owner.stake.skey_file ], ) tx_raw_output = cluster.send_tx( src_address=pool_owner.payment.address, tx_name=f"{temp_template}_rereg_deleg", tx_files=tx_files, ) # check that the balance for source address was correctly updated assert ( cluster.get_address_balance( pool_owner.payment.address) == src_updated_balance - tx_raw_output.fee - cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_owner.payment.address}`" cluster.wait_for_new_epoch(4, padding_seconds=30) # check that the stake address was delegated stake_addr_info = cluster.get_stake_addr_info( pool_owner.stake.address) assert ( stake_addr_info.delegation ), f"Stake address was not delegated yet: {stake_addr_info}" assert pool_id == stake_addr_info.delegation, "Stake address delegated to wrong pool" # check that new rewards were received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # check that pool owner is also receiving rewards assert (orig_user_reward < cluster.get_stake_addr_info( pool_rec["reward"].address).reward_account_balance ), "New reward was not received by pool reward address" # check that pledge is still met after the owner address was used to pay for Txs pool_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=pool_name, pool_id=pool_id) owner_payment_balance = cluster.get_address_balance( pool_owner.payment.address) assert (owner_payment_balance >= pool_data.pool_pledge ), f"Pledge is not met for pool '{pool_name}'!" @allure.link(helpers.get_vcs_link()) def test_no_reward_deregistered_reward_addr( self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Check that the reward address is not receiving rewards when deregistered. The stake pool continues to operate normally and those who delegate to that pool receive rewards. * delegate stake address * wait for first reward * withdraw pool rewards to payment address * deregister the pool reward address * check that the key deposit was returned * check that pool owner is NOT receiving rewards * check that new rewards are received by those delegating to the pool * return the pool to the original state - reregister reward address * check that pool owner is receiving rewards """ pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_reward = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["reward"]) temp_template = common.get_test_id(cluster) pool_id = delegation.get_pool_id( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, pool_name=pool_name) clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-20) init_epoch = cluster.get_epoch() # submit registration certificate and delegate to pool delegation_out = delegation.delegate_stake_addr( cluster_obj=cluster, addrs_data=cluster_manager.cache.addrs_data, temp_template=temp_template, pool_id=pool_id, ) assert ( cluster.get_epoch() == init_epoch ), "Delegation took longer than expected and would affect other checks" LOGGER.info("Waiting 4 epochs for first reward.") cluster.wait_for_new_epoch(new_epochs=4, padding_seconds=10) if not cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance: pytest.skip( f"User of pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # withdraw pool rewards to payment address # use `transaction build` if possible if (VERSIONS.transaction_era >= VERSIONS.ALONZO and VERSIONS.transaction_era == VERSIONS.cluster_era): clusterlib_utils.withdraw_reward_w_build( cluster_obj=cluster, stake_addr_record=pool_reward.stake, dst_addr_record=pool_reward.payment, tx_name=temp_template, ) else: cluster.withdraw_reward( stake_addr_record=pool_reward.stake, dst_addr_record=pool_reward.payment, tx_name=temp_template, ) # deregister the pool reward address stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=pool_reward.stake.vkey_file) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ pool_reward.payment.skey_file, pool_reward.stake.skey_file ], ) src_init_balance = cluster.get_address_balance( pool_reward.payment.address) tx_raw_deregister_output = cluster.send_tx( src_address=pool_reward.payment.address, tx_name=f"{temp_template}_dereg_reward", tx_files=tx_files_deregister, ) with cluster_manager.restart_on_failure(): # check that the key deposit was returned assert ( cluster.get_address_balance( pool_reward.payment.address) == src_init_balance - tx_raw_deregister_output.fee + cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_reward.payment.address}`" # check that the reward address is no longer delegated assert not cluster.get_stake_addr_info( pool_reward.stake.address), "Stake address still delegated" orig_user_reward = cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance cluster.wait_for_new_epoch(3) # check that pool owner is NOT receiving rewards assert (cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance == 0 ), "Pool owner received unexpected rewards" # check that new rewards are received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # Return the pool to the original state - reregister reward address. # fund pool owner's addresses so balance keeps higher than pool pledge after fees etc. # are deducted clusterlib_utils.fund_from_faucet( pool_reward, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) src_updated_balance = cluster.get_address_balance( pool_reward.payment.address) # reregister reward address tx_files = clusterlib.TxFiles( certificate_files=[ pool_rec["reward_addr_registration_cert"], ], signing_key_files=[ pool_reward.payment.skey_file, pool_reward.stake.skey_file ], ) tx_raw_output = cluster.send_tx( src_address=pool_reward.payment.address, tx_name=f"{temp_template}_rereg_deleg", tx_files=tx_files, ) # check that the balance for source address was correctly updated assert ( cluster.get_address_balance( pool_reward.payment.address) == src_updated_balance - tx_raw_output.fee - cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_reward.payment.address}`" cluster.wait_for_new_epoch(4, padding_seconds=30) # check that new rewards were received by those delegating to the pool assert (orig_user_reward < cluster.get_stake_addr_info( delegation_out.pool_user.stake.address).reward_account_balance ), "New reward was not received by stake address" # check that pool owner is also receiving rewards assert (cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance > 0), "New reward was not received by pool reward address" # check that pledge is still met after the owner address was used to pay for Txs pool_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=pool_name, pool_id=pool_id) owner_payment_balance = cluster.get_address_balance( pool_reward.payment.address) assert (owner_payment_balance >= pool_data.pool_pledge ), f"Pledge is not met for pool '{pool_name}'!" @allure.link(helpers.get_vcs_link()) def test_deregister_reward_addr_retire_pool( # noqa: C901 self, cluster_manager: cluster_management.ClusterManager, cluster_lock_pool2: clusterlib.ClusterLib, ): """Test deregistering reward address and retiring stake pool. The pool deposit is lost when reward address is deregistered before the pool is retired. * wait for first reward for the pool * withdraw pool rewards to payment address * deregister the pool reward address * check that the key deposit was returned * check that pool owner is NOT receiving rewards * deregister stake pool * check that the pool deposit was NOT returned to reward or stake address * return the pool to the original state - reregister the pool, register the reward address, delegate the stake address to the pool * check that pool deposit was needed * check that pool owner is receiving rewards """ # pylint: disable=too-many-statements,too-many-locals __: Any # mypy workaround pool_name = cluster_management.Resources.POOL2 cluster = cluster_lock_pool2 pool_rec = cluster_manager.cache.addrs_data[pool_name] pool_reward = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["reward"]) pool_owner = clusterlib.PoolUser(payment=pool_rec["payment"], stake=pool_rec["stake"]) pool_opcert_file: Path = pool_rec["pool_operational_cert"] temp_template = common.get_test_id(cluster) LOGGER.info("Waiting up to 4 full epochs for first reward.") for i in range(5): if i > 0: cluster.wait_for_new_epoch(padding_seconds=10) if cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance: break else: pytest.skip( f"Pool '{pool_name}' hasn't received any rewards, cannot continue." ) # make sure we have enough time to finish reward address deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) # withdraw pool rewards to payment address cluster.withdraw_reward( stake_addr_record=pool_reward.stake, dst_addr_record=pool_reward.payment, tx_name=temp_template, ) # deregister the pool reward address stake_addr_dereg_cert = cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr0", stake_vkey_file=pool_reward.stake.vkey_file) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ pool_reward.payment.skey_file, pool_reward.stake.skey_file ], ) src_init_balance = cluster.get_address_balance( pool_reward.payment.address) tx_raw_deregister_output = cluster.send_tx( src_address=pool_reward.payment.address, tx_name=f"{temp_template}_dereg_reward", tx_files=tx_files_deregister, ) with cluster_manager.restart_on_failure(): # check that the key deposit was returned assert ( cluster.get_address_balance( pool_reward.payment.address) == src_init_balance - tx_raw_deregister_output.fee + cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_reward.payment.address}`" # check that the reward address is no longer delegated assert not cluster.get_stake_addr_info( pool_reward.stake.address), "Stake address still delegated" cluster.wait_for_new_epoch(3) # check that pool owner is NOT receiving rewards assert (cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance == 0 ), "Pool owner received unexpected rewards" # fund pool owner's addresses so balance keeps higher than pool pledge after fees etc. # are deducted clusterlib_utils.fund_from_faucet( pool_owner, cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], amount=900_000_000, force=True, ) # make sure we have enough time to finish pool deregistration in one epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-40) src_dereg_balance = cluster.get_address_balance( pool_owner.payment.address) stake_acount_balance = cluster.get_stake_addr_info( pool_owner.stake.address).reward_account_balance reward_acount_balance = cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance node_cold = pool_rec["cold_key_pair"] pool_id = cluster.get_stake_pool_id(node_cold.vkey_file) # deregister stake pool depoch = cluster.get_epoch() + 1 __, tx_raw_output = cluster.deregister_stake_pool( pool_owners=[pool_owner], cold_key_pair=node_cold, epoch=depoch, pool_name=pool_name, tx_name=temp_template, ) assert cluster.get_pool_params(pool_id).retiring == depoch # check that the pool was deregistered cluster.wait_for_new_epoch() assert not cluster.get_pool_params( pool_id ).pool_params, f"The pool {pool_id} was not deregistered" # check command kes-period-info case: de-register pool # TODO: the query is currently broken kes_query_currently_broken = False try: kes_period_info = cluster.get_kes_period_info(pool_opcert_file) except clusterlib.CLIError as err: if "currentlyBroken" not in str(err): raise kes_query_currently_broken = True if not kes_query_currently_broken: kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.ALL_VALID) # check that the balance for source address was correctly updated assert src_dereg_balance - tx_raw_output.fee == cluster.get_address_balance( pool_owner.payment.address) # check that the pool deposit was NOT returned to reward or stake address assert (cluster.get_stake_addr_info( pool_owner.stake.address).reward_account_balance == stake_acount_balance) assert (cluster.get_stake_addr_info( pool_reward.stake.address).reward_account_balance == reward_acount_balance) # Return the pool to the original state - reregister the pool, register # the reward address, delegate the stake address to the pool. src_updated_balance = cluster.get_address_balance( pool_reward.payment.address) # reregister the pool by resubmitting the pool registration certificate, # delegate stake address to pool again, reregister reward address tx_files = clusterlib.TxFiles( certificate_files=[ pool_rec["reward_addr_registration_cert"], pool_rec["pool_registration_cert"], pool_rec["stake_addr_delegation_cert"], ], signing_key_files=[ pool_rec["payment"].skey_file, pool_rec["stake"].skey_file, pool_rec["reward"].skey_file, node_cold.skey_file, ], ) tx_raw_output = cluster.send_tx( src_address=pool_reward.payment.address, tx_name=f"{temp_template}_rereg_pool", tx_files=tx_files, ) # check command kes-period-info case: re-register pool, check without # waiting to take effect if not kes_query_currently_broken: kes_period_info = cluster.get_kes_period_info(pool_opcert_file) kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.ALL_VALID) # check that the balance for source address was correctly updated and that the # pool deposit was needed assert ( cluster.get_address_balance( pool_reward.payment.address) == src_updated_balance - tx_raw_output.fee - cluster.get_pool_deposit() - cluster.get_address_deposit() ), f"Incorrect balance for source address `{pool_reward.payment.address}`" LOGGER.info( "Waiting up to 5 epochs for stake pool to be reregistered.") for __ in range(5): cluster.wait_for_new_epoch(padding_seconds=10) if pool_id in cluster.get_stake_distribution(): break else: raise AssertionError( f"Stake pool `{pool_id}` not registered even after 5 epochs." ) # check command kes-period-info case: re-register pool if not kes_query_currently_broken: kes_period_info = cluster.get_kes_period_info(pool_opcert_file) kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.ALL_VALID) # wait before checking delegation and rewards cluster.wait_for_new_epoch(3, padding_seconds=30) # check that the stake address was delegated stake_addr_info = cluster.get_stake_addr_info( pool_owner.stake.address) assert ( stake_addr_info.delegation ), f"Stake address was not delegated yet: {stake_addr_info}" assert pool_id == stake_addr_info.delegation, "Stake address delegated to wrong pool" # check that pool owner is receiving rewards assert cluster.get_stake_addr_info( pool_reward.stake.address ).reward_account_balance, "New reward was not received by pool reward address" # check that pledge is still met after the owner address was used to pay for Txs pool_data = clusterlib_utils.load_registered_pool_data( cluster_obj=cluster, pool_name=pool_name, pool_id=pool_id) owner_payment_balance = cluster.get_address_balance( pool_owner.payment.address) assert (owner_payment_balance >= pool_data.pool_pledge ), f"Pledge is not met for pool '{pool_name}'!"
class TestFee: """General fees tests.""" @pytest.fixture def payment_addrs( self, cluster_manager: parallel_run.ClusterManager, cluster: clusterlib.ClusterLib, ) -> List[clusterlib.AddressRecord]: """Create 2 new payment addresses.""" data_key = id(TestFee) cached_value = cluster_manager.cache.test_data.get(data_key) if cached_value: return cached_value # type: ignore addrs = clusterlib_utils.create_payment_addr_records( f"addr_test_fee_ci{cluster_manager.cluster_instance}_0", f"addr_test_fee_ci{cluster_manager.cluster_instance}_1", cluster_obj=cluster, ) cluster_manager.cache.test_data[data_key] = addrs # fund source addresses clusterlib_utils.fund_from_faucet( addrs[0], cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) return addrs @hypothesis.given(fee=st.integers(max_value=-1)) @helpers.HYPOTHESIS_SETTINGS @allure.link(helpers.get_vcs_link()) def test_negative_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee: int, ): """Try to send a transaction with negative fee (property-based test). Expect failure. """ temp_template = f"{helpers.get_func_name()}_{clusterlib_utils.get_timestamped_rand_str()}" src_address = payment_addrs[0].address dst_address = payment_addrs[1].address destinations = [clusterlib.TxOut(address=dst_address, amount=10)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=fee, ) assert "option --fee: cannot parse value" in str(excinfo.value) @pytest.mark.parametrize("fee_change", (0, 1.1, 1.5, 2)) @allure.link(helpers.get_vcs_link()) def test_smaller_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee_change: float, ): """Try to send a transaction with smaller-than-expected fee. Expect failure. """ temp_template = f"{helpers.get_func_name()}_{fee_change}" src_address = payment_addrs[0].address dst_address = payment_addrs[1].address destinations = [clusterlib.TxOut(address=dst_address, amount=10)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) fee = 0.0 if fee_change: fee = (cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) / fee_change) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=int(fee), ) assert "FeeTooSmallUTxO" in str(excinfo.value) @pytest.mark.parametrize("fee_add", (0, 1000, 100_000, 1_000_000)) @allure.link(helpers.get_vcs_link()) def test_expected_or_higher_fee( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord], fee_add: int, ): """Send a transaction with fee that is same or higher than expected.""" temp_template = f"{helpers.get_func_name()}_{fee_add}" amount = 100 src_address = payment_addrs[0].address dst_address = payment_addrs[1].address src_init_balance = cluster.get_address_balance(src_address) dst_init_balance = cluster.get_address_balance(dst_address) destinations = [clusterlib.TxOut(address=dst_address, amount=amount)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) fee = (cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) + fee_add) tx_raw_output = cluster.send_funds( src_address=src_address, destinations=destinations, tx_name=temp_template, tx_files=tx_files, fee=fee, ) cluster.wait_for_new_block(new_blocks=2) assert tx_raw_output.fee == fee, "The actual fee doesn't match the specified fee" assert (cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - len(destinations) * amount ), f"Incorrect balance for source address `{src_address}`" assert (cluster.get_address_balance(dst_address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{dst_address}`"
class TestExpectedFees: """Test expected fees.""" @pytest.fixture def pool_users( self, cluster_manager: parallel_run.ClusterManager, cluster: clusterlib.ClusterLib, ) -> List[clusterlib.PoolUser]: """Create pool users.""" data_key = id(TestExpectedFees) cached_value = cluster_manager.cache.test_data.get(data_key) if cached_value: return cached_value # type: ignore created_users = clusterlib_utils.create_pool_users( cluster_obj=cluster, name_template= f"test_expected_fees_ci{cluster_manager.cluster_instance}", no_of_addr=201, ) cluster_manager.cache.test_data[data_key] = created_users # fund source addresses clusterlib_utils.fund_from_faucet( *created_users[:10], cluster_obj=cluster, faucet_data=cluster_manager.cache.addrs_data["user1"], ) return created_users def _create_pool_certificates( self, cluster_obj: clusterlib.ClusterLib, pool_owners: List[clusterlib.PoolUser], temp_template: str, pool_data: clusterlib.PoolData, ) -> Tuple[str, clusterlib.TxFiles]: """Create certificates for registering a stake pool, delegating stake address.""" # create node VRF key pair node_vrf = cluster_obj.gen_vrf_key_pair(node_name=pool_data.pool_name) # create node cold key pair and counter node_cold = cluster_obj.gen_cold_key_pair_and_counter( node_name=pool_data.pool_name) # create stake address registration certs stake_addr_reg_cert_files = [ cluster_obj.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file) for i, p in enumerate(pool_owners) ] # create stake address delegation cert stake_addr_deleg_cert_files = [ cluster_obj.gen_stake_addr_delegation_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file, cold_vkey_file=node_cold.vkey_file, ) for i, p in enumerate(pool_owners) ] # create stake pool registration cert pool_reg_cert_file = cluster_obj.gen_pool_registration_cert( pool_data=pool_data, vrf_vkey_file=node_vrf.vkey_file, cold_vkey_file=node_cold.vkey_file, owner_stake_vkey_files=[p.stake.vkey_file for p in pool_owners], ) src_address = pool_owners[0].payment.address # register and delegate stake address, create and register pool tx_files = clusterlib.TxFiles( certificate_files=[ pool_reg_cert_file, *stake_addr_reg_cert_files, *stake_addr_deleg_cert_files, ], signing_key_files=[ *[p.payment.skey_file for p in pool_owners], *[p.stake.skey_file for p in pool_owners], node_cold.skey_file, ], ) return src_address, tx_files def _from_to_transactions( self, cluster_obj: clusterlib.ClusterLib, tx_name: str, pool_users: List[clusterlib.PoolUser], from_num: int, to_num: int, amount_expected: Tuple[int, int], ): """Check fees for 1 tx from `from_num` payment addresses to `to_num` payment addresses.""" amount, expected_fee = amount_expected src_address = pool_users[0].payment.address # addr1..addr<from_num+1> from_addr_recs = [p.payment for p in pool_users[1:from_num + 1]] # addr<from_num+1>..addr<from_num+to_num+1> dst_addresses = [ pool_users[i].payment.address for i in range(from_num + 1, from_num + to_num + 1) ] # create TX data _txins = [cluster_obj.get_utxo(r.address) for r in from_addr_recs] # flatten the list of lists that is _txins txins = list(itertools.chain.from_iterable(_txins)) txouts = [ clusterlib.TxOut(address=addr, amount=amount) for addr in dst_addresses ] tx_files = clusterlib.TxFiles( signing_key_files=[r.skey_file for r in from_addr_recs]) # calculate TX fee tx_fee = cluster_obj.calculate_tx_fee(src_address=src_address, tx_name=tx_name, txins=txins, txouts=txouts, tx_files=tx_files) assert tx_fee == expected_fee, "Expected fee doesn't match the actual fee" @pytest.mark.parametrize("addr_fee", [(1, 197929), (3, 234185), (5, 270441), (10, 361081)]) @allure.link(helpers.get_vcs_link()) def test_pool_registration_fees( self, cluster: clusterlib.ClusterLib, temp_dir: Path, pool_users: List[clusterlib.PoolUser], addr_fee: Tuple[int, int], ): """Test pool registration fees.""" no_of_addr, expected_fee = addr_fee rand_str = clusterlib.get_rand_str(4) temp_template = f"{helpers.get_func_name()}_{rand_str}_{no_of_addr}" pool_name = f"pool_{rand_str}" pool_metadata = { "name": pool_name, "description": "Shelley QA E2E test Test", "ticker": "QA1", "homepage": "www.test1.com", } pool_metadata_file = helpers.write_json( temp_dir / f"{pool_name}_registration_metadata.json", pool_metadata) pool_data = clusterlib.PoolData( pool_name=pool_name, pool_pledge=1000, pool_cost=15, pool_margin=0.2, pool_metadata_url="https://www.where_metadata_file_is_located.com", pool_metadata_hash=cluster.gen_pool_metadata_hash( pool_metadata_file), ) # create pool owners selected_owners = pool_users[:no_of_addr] # create certificates src_address, tx_files = self._create_pool_certificates( cluster_obj=cluster, pool_owners=selected_owners, temp_template=temp_template, pool_data=pool_data, ) # calculate TX fee tx_fee = cluster.calculate_tx_fee(src_address=src_address, tx_name=temp_template, tx_files=tx_files) assert tx_fee == expected_fee, "Expected fee doesn't match the actual fee" @pytest.mark.parametrize("addr_fee", [(1, 185345), (3, 210337), (5, 235329), (10, 297809)]) @allure.link(helpers.get_vcs_link()) def test_pool_deregistration_fees( self, cluster: clusterlib.ClusterLib, temp_dir: Path, pool_users: List[clusterlib.PoolUser], addr_fee: Tuple[int, int], ): """Test pool deregistration fees.""" no_of_addr, expected_fee = addr_fee rand_str = clusterlib.get_rand_str(4) temp_template = f"{helpers.get_func_name()}_{rand_str}_{no_of_addr}" src_address = pool_users[0].payment.address pool_name = f"pool_{rand_str}" pool_metadata = { "name": pool_name, "description": "Shelley QA E2E test Test", "ticker": "QA1", "homepage": "www.test1.com", } pool_metadata_file = helpers.write_json( temp_dir / f"{pool_name}_registration_metadata.json", pool_metadata) pool_data = clusterlib.PoolData( pool_name=pool_name, pool_pledge=222, pool_cost=123, pool_margin=0.512, pool_metadata_url="https://www.where_metadata_file_is_located.com", pool_metadata_hash=cluster.gen_pool_metadata_hash( pool_metadata_file), ) # create pool owners selected_owners = pool_users[:no_of_addr] # create node cold key pair and counter node_cold = cluster.gen_cold_key_pair_and_counter( node_name=pool_data.pool_name) # create deregistration certificate pool_dereg_cert_file = cluster.gen_pool_deregistration_cert( pool_name=pool_data.pool_name, cold_vkey_file=node_cold.vkey_file, epoch=cluster.get_last_block_epoch() + 1, ) # submit the pool deregistration certificate through a tx tx_files = clusterlib.TxFiles( certificate_files=[pool_dereg_cert_file], signing_key_files=[ *[p.payment.skey_file for p in selected_owners], *[p.stake.skey_file for p in selected_owners], node_cold.skey_file, ], ) # calculate TX fee tx_fee = cluster.calculate_tx_fee(src_address=src_address, tx_name=temp_template, tx_files=tx_files) assert tx_fee == expected_fee, "Expected fee doesn't match the actual fee" @pytest.mark.parametrize("addr_fee", [(1, 179141), (3, 207125), (5, 235109), (10, 305069)]) @allure.link(helpers.get_vcs_link()) def test_addr_registration_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], addr_fee: Tuple[int, int], ): """Test stake address registration fees.""" no_of_addr, expected_fee = addr_fee temp_template = f"{helpers.get_func_name()}_{no_of_addr}" src_address = pool_users[0].payment.address selected_users = pool_users[:no_of_addr] stake_addr_reg_certs = [ cluster.gen_stake_addr_registration_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file) for i, p in enumerate(selected_users) ] # create TX data tx_files = clusterlib.TxFiles( certificate_files=[*stake_addr_reg_certs], signing_key_files=[ *[p.payment.skey_file for p in selected_users], *[p.stake.skey_file for p in selected_users], ], ) # calculate TX fee tx_fee = cluster.calculate_tx_fee(src_address=src_address, tx_name=temp_template, tx_files=tx_files) assert tx_fee == expected_fee, "Expected fee doesn't match the actual fee" @pytest.mark.parametrize("addr_fee", [(1, 179141), (3, 207125), (5, 235109), (10, 305069)]) @allure.link(helpers.get_vcs_link()) def test_addr_deregistration_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], addr_fee: Tuple[int, int], ): """Test stake address deregistration fees.""" no_of_addr, expected_fee = addr_fee temp_template = f"{helpers.get_func_name()}_{no_of_addr}" src_address = pool_users[0].payment.address selected_users = pool_users[:no_of_addr] stake_addr_dereg_certs = [ cluster.gen_stake_addr_deregistration_cert( addr_name=f"{temp_template}_addr{i}", stake_vkey_file=p.stake.vkey_file) for i, p in enumerate(selected_users) ] # create TX data tx_files = clusterlib.TxFiles( certificate_files=[*stake_addr_dereg_certs], signing_key_files=[ *[p.payment.skey_file for p in selected_users], *[p.stake.skey_file for p in selected_users], ], ) # calculate TX fee tx_fee = cluster.calculate_tx_fee(src_address=src_address, tx_name=temp_template, tx_files=tx_files) assert tx_fee == expected_fee, "Expected fee doesn't match the actual fee" @pytest.mark.parametrize("amount_expected", [(1, 176853), (100, 176897), (11_000, 176941), (100_000, 177029)]) @allure.link(helpers.get_vcs_link()) def test_transaction_to_1_addr_from_1_addr_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], amount_expected: Tuple[int, int], ): """Test fees for 1 tx from 1 payment address to 1 payment address.""" temp_template = f"{helpers.get_func_name()}_{amount_expected[0]}" self._from_to_transactions( cluster_obj=cluster, tx_name=temp_template, pool_users=pool_users, from_num=1, to_num=1, amount_expected=amount_expected, ) @pytest.mark.parametrize("amount_expected", [(1, 226749), (100, 227189), (11_000, 227629), (100_000, 228509)]) @allure.link(helpers.get_vcs_link()) def test_transaction_to_10_addrs_from_1_addr_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], amount_expected: Tuple[int, int], ): """Test fees for 1 tx from 1 payment address to 10 payment addresses.""" temp_template = f"{helpers.get_func_name()}_{amount_expected[0]}" self._from_to_transactions( cluster_obj=cluster, tx_name=temp_template, pool_users=pool_users, from_num=1, to_num=10, amount_expected=amount_expected, ) @pytest.mark.parametrize("amount_expected", [(1, 259837), (100, 259881), (11_000, 259925), (100_000, 260013)]) @allure.link(helpers.get_vcs_link()) def test_transaction_to_1_addr_from_10_addrs_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], amount_expected: Tuple[int, int], ): """Test fees for 1 tx from 10 payment addresses to 1 payment address.""" temp_template = f"{helpers.get_func_name()}_{amount_expected[0]}" self._from_to_transactions( cluster_obj=cluster, tx_name=temp_template, pool_users=pool_users, from_num=10, to_num=1, amount_expected=amount_expected, ) @pytest.mark.parametrize("amount_expected", [(1, 309733), (100, 310173), (11_000, 310613), (100_000, 311493)]) @allure.link(helpers.get_vcs_link()) def test_transaction_to_10_addrs_from_10_addrs_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], amount_expected: Tuple[int, int], ): """Test fees for 1 tx from 10 payment addresses to 10 payment addresses.""" temp_template = f"{helpers.get_func_name()}_{amount_expected[0]}" self._from_to_transactions( cluster_obj=cluster, tx_name=temp_template, pool_users=pool_users, from_num=10, to_num=10, amount_expected=amount_expected, ) @pytest.mark.parametrize("amount_expected", [(1, 1371057), (100, 1375457), (11_000, 1379857), (100_000, 1388657)]) @allure.link(helpers.get_vcs_link()) def test_transaction_to_100_addrs_from_100_addrs_fees( self, cluster: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], amount_expected: Tuple[int, int], ): """Test fees for 1 tx from 100 payment addresses to 100 payment addresses.""" temp_template = f"{helpers.get_func_name()}_{amount_expected[0]}" self._from_to_transactions( cluster_obj=cluster, tx_name=temp_template, pool_users=pool_users, from_num=100, to_num=100, amount_expected=amount_expected, )
class TestKES: """Basic tests for KES period.""" @pytest.mark.run(order=3) @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_expired_kes( self, cluster_kes: clusterlib.ClusterLib, ): """Test expired KES.""" cluster = cluster_kes expire_timeout = int(cluster.slots_per_kes_period * cluster.slot_length * cluster.max_kes_evolutions + 1) expected_errors = [ ("*.stdout", "TraceNoLedgerView"), ("*.stdout", "KESKeyAlreadyPoisoned"), ("*.stdout", "KESCouldNotEvolve"), ("*.stdout", r"ExceededTimeLimit \(ChainSync"), ] with logfiles.expect_errors(expected_errors): LOGGER.info( f"Waiting for {expire_timeout} sec for KES expiration.") time.sleep(expire_timeout) init_slot = cluster.get_slot_no() kes_period_timeout = int(cluster.slots_per_kes_period * cluster.slot_length + 1) LOGGER.info( f"Waiting for {kes_period_timeout} sec for next KES period.") time.sleep(kes_period_timeout) assert cluster.get_slot_no() == init_slot, "Unexpected new slots" @pytest.mark.run(order=1) @allure.link(helpers.get_vcs_link()) def test_opcert_past_kes_period( self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, ): """Start a stake pool with an operational certificate created with expired `--kes-period`. * generate new operational certificate with `--kes-period` in the past * restart the node with the new operational certificate * check that the pool is not producing any blocks * generate new operational certificate with valid `--kes-period` and restart the node * check that the pool is producing blocks again """ pool_name = "node-pool2" node_name = "pool2" cluster = cluster_lock_pool2 temp_template = helpers.get_func_name() pool_rec = cluster_manager.cache.addrs_data[pool_name] node_cold = pool_rec["cold_key_pair"] stake_pool_id = cluster.get_stake_pool_id(node_cold.vkey_file) stake_pool_id_dec = helpers.decode_bech32(stake_pool_id) opcert_file: Path = pool_rec["pool_operational_cert"] def _wait_epoch_chores(this_epoch: int): # wait for next epoch if cluster.get_epoch() == this_epoch: cluster.wait_for_new_epoch() # wait for the end of the epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=-19, stop=-9) # save ledger state clusterlib_utils.save_ledger_state( cluster_obj=cluster, state_name=f"{temp_template}_{cluster.get_epoch()}", ) with cluster_manager.restart_on_failure(): # generate new operational certificate with `--kes-period` in the past invalid_opcert_file = cluster.gen_node_operational_cert( node_name=node_name, kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_kes_period() - 5, ) expected_errors = [ (f"{node_name}.stdout", "TPraosCannotForgeKeyNotUsableYet"), ] with logfiles.expect_errors(expected_errors): # restart the node with the new operational certificate logfiles.add_ignore_rule("*.stdout", "MuxBearerClosed") shutil.copy(invalid_opcert_file, opcert_file) cluster_nodes.restart_node(node_name) cluster.wait_for_new_epoch() LOGGER.info("Checking blocks production for 5 epochs.") this_epoch = -1 for __ in range(5): _wait_epoch_chores(this_epoch) this_epoch = cluster.get_epoch() # check that the pool is not producing any blocks blocks_made = clusterlib_utils.get_ledger_state( cluster_obj=cluster)["blocksCurrent"] if blocks_made: assert ( stake_pool_id_dec not in blocks_made ), f"The pool '{pool_name}' has produced blocks in epoch {this_epoch}" # generate new operational certificate with valid `--kes-period` os.remove(opcert_file) valid_opcert_file = cluster.gen_node_operational_cert( node_name=node_name, kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_kes_period(), ) # copy the new certificate and restart the node shutil.move(str(valid_opcert_file), str(opcert_file)) cluster_nodes.restart_node(node_name) cluster.wait_for_new_epoch() LOGGER.info("Checking blocks production for another 5 epochs.") blocks_made_db = [] this_epoch = cluster.get_epoch() active_again_epoch = this_epoch for __ in range(5): _wait_epoch_chores(this_epoch) this_epoch = cluster.get_epoch() # check that the pool is producing blocks blocks_made = clusterlib_utils.get_ledger_state( cluster_obj=cluster)["blocksCurrent"] blocks_made_db.append(stake_pool_id_dec in blocks_made) assert any(blocks_made_db), ( f"The pool '{pool_name}' has not produced any blocks " f"since epoch {active_again_epoch}") @pytest.mark.run(order=2) @allure.link(helpers.get_vcs_link()) def test_update_valid_opcert( self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, ): """Update a valid operational certificate with another valid operational certificate. * generate new operational certificate with valid `--kes-period` * restart the node with the new operational certificate * check that the pool is still producing blocks """ pool_name = "node-pool2" node_name = "pool2" cluster = cluster_lock_pool2 temp_template = helpers.get_func_name() pool_rec = cluster_manager.cache.addrs_data[pool_name] node_cold = pool_rec["cold_key_pair"] stake_pool_id = cluster.get_stake_pool_id(node_cold.vkey_file) stake_pool_id_dec = helpers.decode_bech32(stake_pool_id) opcert_file = pool_rec["pool_operational_cert"] with cluster_manager.restart_on_failure(): # generate new operational certificate with valid `--kes-period` new_opcert_file = cluster.gen_node_operational_cert( node_name=node_name, kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_kes_period(), ) # restart the node with the new operational certificate logfiles.add_ignore_rule("*.stdout", "MuxBearerClosed") shutil.copy(new_opcert_file, opcert_file) cluster_nodes.restart_node(node_name) LOGGER.info("Checking blocks production for 5 epochs.") blocks_made_db = [] this_epoch = -1 updated_epoch = cluster.get_epoch() for __ in range(5): # wait for next epoch if cluster.get_epoch() == this_epoch: cluster.wait_for_new_epoch() # wait for the end of the epoch clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=-19, stop=-9) this_epoch = cluster.get_epoch() ledger_state = clusterlib_utils.get_ledger_state( cluster_obj=cluster) # save ledger state clusterlib_utils.save_ledger_state( cluster_obj=cluster, state_name=f"{temp_template}_{this_epoch}", ledger_state=ledger_state, ) # check that the pool is still producing blocks blocks_made = ledger_state["blocksCurrent"] blocks_made_db.append(stake_pool_id_dec in blocks_made) assert any(blocks_made_db), ( f"The pool '{pool_name}' has not produced any blocks " f"since epoch {updated_epoch}") @allure.link(helpers.get_vcs_link()) @pytest.mark.skipif( bool(configuration.TX_ERA), reason="different TX eras doesn't affect this test, pointless to run", ) def test_no_kes_period_arg( self, cluster: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, temp_dir: Path, ): """Try to generate new operational certificate without specifying the `--kes-period`. Expect failure. """ pool_name = "node-pool2" pool_rec = cluster_manager.cache.addrs_data[pool_name] temp_template = helpers.get_func_name() out_file = temp_dir / f"{temp_template}_shouldnt_exist.opcert" # try to generate new operational certificate without specifying the `--kes-period` with pytest.raises(clusterlib.CLIError) as excinfo: cluster.cli([ "node", "issue-op-cert", "--kes-verification-key-file", str(pool_rec["kes_key_pair"].vkey_file), "--cold-signing-key-file", str(pool_rec["cold_key_pair"].skey_file), "--operational-certificate-issue-counter", str(pool_rec["cold_key_pair"].counter_file), "--out-file", str(out_file), ]) assert "Missing: --kes-period NATURAL" in str(excinfo.value) assert not out_file.exists( ), "New operational certificate was generated"
class TestKES: """Basic tests for KES period.""" @allure.link(helpers.get_vcs_link()) @pytest.mark.order(5) @pytest.mark.long def test_expired_kes( self, cluster_kes: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, worker_id: str, ): """Test expired KES. * start local cluster instance configured with short KES period and low number of key evolutions, so KES expires soon on all pools * refresh opcert on 2 of the 3 pools, so KES doesn't expire on those 2 pools and the pools keep minting blocks * wait for KES expiration on the selected pool * check that the pool with expired KES didn't mint blocks in an epoch that followed after KES expiration * check KES period info command with an operational certificate with an expired KES * check KES period info command with operational certificates with a valid KES """ cluster = cluster_kes temp_template = common.get_test_id(cluster) expire_timeout = 200 expire_node_name = "pool1" expire_pool_name = f"node-{expire_node_name}" expire_pool_rec = cluster_manager.cache.addrs_data[expire_pool_name] expire_pool_id = cluster.get_stake_pool_id(expire_pool_rec["cold_key_pair"].vkey_file) expire_pool_id_dec = helpers.decode_bech32(expire_pool_id) # refresh opcert on 2 of the 3 pools, so KES doesn't expire on those 2 pools and # the pools keep minting blocks refreshed_nodes = ["pool2", "pool3"] def _refresh_opcerts(): for n in refreshed_nodes: refreshed_pool_rec = cluster_manager.cache.addrs_data[f"node-{n}"] refreshed_opcert_file = cluster.gen_node_operational_cert( node_name=f"{n}_refreshed_opcert", kes_vkey_file=refreshed_pool_rec["kes_key_pair"].vkey_file, cold_skey_file=refreshed_pool_rec["cold_key_pair"].skey_file, cold_counter_file=refreshed_pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_kes_period(), ) shutil.copy(refreshed_opcert_file, refreshed_pool_rec["pool_operational_cert"]) cluster_nodes.restart_nodes(refreshed_nodes) _refresh_opcerts() expected_err_regexes = ["KESKeyAlreadyPoisoned", "KESCouldNotEvolve"] # ignore expected errors in bft1 node log file, as bft1 opcert will not get refreshed logfiles.add_ignore_rule( files_glob="bft1.stdout", regex="|".join(expected_err_regexes), ignore_file_id=worker_id, ) # search for expected errors only in log file corresponding to pool with expired KES expected_errors = [(f"{expire_node_name}.stdout", err) for err in expected_err_regexes] this_epoch = -1 with logfiles.expect_errors(expected_errors, ignore_file_id=worker_id): LOGGER.info( f"{datetime.datetime.now()}: Waiting for {expire_timeout} sec for KES expiration." ) time.sleep(expire_timeout) _wait_epoch_chores( cluster_obj=cluster, temp_template=temp_template, this_epoch=this_epoch ) this_epoch = cluster.get_epoch() # check that the pool is not producing any blocks blocks_made = clusterlib_utils.get_ledger_state(cluster_obj=cluster)["blocksCurrent"] if blocks_made: assert ( expire_pool_id_dec not in blocks_made ), f"The pool '{expire_pool_name}' has minted blocks in epoch {this_epoch}" # refresh opcerts one more time _refresh_opcerts() LOGGER.info( f"{datetime.datetime.now()}: Waiting 120 secs to make sure the expected errors " "make it to log files." ) time.sleep(120) # check kes-period-info with an operational certificate with KES expired # TODO: the query is currently broken kes_query_currently_broken = False try: kes_info_expired = cluster.get_kes_period_info( opcert_file=expire_pool_rec["pool_operational_cert"] ) except clusterlib.CLIError as err: if "currentlyBroken" not in str(err): raise kes_query_currently_broken = True if kes_query_currently_broken: pytest.xfail("`query kes-period-info` is currently broken") else: kes.check_kes_period_info_result( kes_output=kes_info_expired, expected_scenario=kes.KesScenarios.INVALID_KES_PERIOD ) # check kes-period-info with valid operational certificates for n in refreshed_nodes: refreshed_pool_rec = cluster_manager.cache.addrs_data[f"node-{n}"] kes_info_valid = cluster.get_kes_period_info( opcert_file=refreshed_pool_rec["pool_operational_cert"] ) kes.check_kes_period_info_result( kes_output=kes_info_valid, expected_scenario=kes.KesScenarios.ALL_VALID ) @allure.link(helpers.get_vcs_link()) @pytest.mark.order(6) @pytest.mark.long def test_opcert_future_kes_period( # noqa: C901 self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, ): """Start a stake pool with an operational certificate created with invalid `--kes-period`. * generate new operational certificate with `--kes-period` in the future * restart the node with the new operational certificate * check that the pool is not producing any blocks * if network era > Alonzo - generate new operational certificate with valid `--kes-period`, but counter value +2 from last used operational ceritificate - restart the node - check that the pool is not producing any blocks * generate new operational certificate with valid `--kes-period` and restart the node * check that the pool is producing blocks again """ # pylint: disable=too-many-statements,too-many-branches pool_name = cluster_management.Resources.POOL2 node_name = "pool2" cluster = cluster_lock_pool2 temp_template = common.get_test_id(cluster) pool_rec = cluster_manager.cache.addrs_data[pool_name] node_cold = pool_rec["cold_key_pair"] stake_pool_id = cluster.get_stake_pool_id(node_cold.vkey_file) stake_pool_id_dec = helpers.decode_bech32(stake_pool_id) opcert_file: Path = pool_rec["pool_operational_cert"] cold_counter_file: Path = pool_rec["cold_key_pair"].counter_file expected_errors = [ (f"{node_name}.stdout", "PraosCannotForgeKeyNotUsableYet"), ] if VERSIONS.cluster_era > VERSIONS.ALONZO: expected_errors.append((f"{node_name}.stdout", "CounterOverIncrementedOCERT")) # In Babbage we get `CounterOverIncrementedOCERT` error if counter for new opcert # is not exactly +1 from last used opcert. We'll backup the original counter # file so we can use it for issuing next valid opcert. cold_counter_file_orig = Path( f"{cold_counter_file.stem}_orig{cold_counter_file.suffix}" ).resolve() shutil.copy(cold_counter_file, cold_counter_file_orig) logfiles.add_ignore_rule( files_glob="*.stdout", regex="MuxBearerClosed|CounterOverIncrementedOCERT", ignore_file_id=cluster_manager.worker_id, ) # generate new operational certificate with `--kes-period` in the future invalid_opcert_file = cluster.gen_node_operational_cert( node_name=f"{node_name}_invalid_opcert_file", kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=cold_counter_file, kes_period=cluster.get_kes_period() + 100, ) kes_query_currently_broken = False with cluster_manager.restart_on_failure(): with logfiles.expect_errors(expected_errors, ignore_file_id=cluster_manager.worker_id): # restart the node with the new operational certificate shutil.copy(invalid_opcert_file, opcert_file) cluster_nodes.restart_nodes([node_name]) cluster.wait_for_new_epoch() LOGGER.info("Checking blocks production for 4 epochs.") this_epoch = -1 for invalid_opcert_epoch in range(4): _wait_epoch_chores( cluster_obj=cluster, temp_template=temp_template, this_epoch=this_epoch ) this_epoch = cluster.get_epoch() # check that the pool is not producing any blocks blocks_made = clusterlib_utils.get_ledger_state(cluster_obj=cluster)[ "blocksCurrent" ] if blocks_made: assert ( stake_pool_id_dec not in blocks_made ), f"The pool '{pool_name}' has produced blocks in epoch {this_epoch}" if invalid_opcert_epoch == 1: # check kes-period-info with operational certificate with # invalid `--kes-period` # TODO: the query is currently broken try: kes_period_info = cluster.get_kes_period_info(invalid_opcert_file) except clusterlib.CLIError as err: if "currentlyBroken" not in str(err): raise kes_query_currently_broken = True if not kes_query_currently_broken: kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.INVALID_KES_PERIOD, ) # test the `CounterOverIncrementedOCERT` error - the counter will now be +2 from # last used opcert counter value if invalid_opcert_epoch == 2 and VERSIONS.cluster_era > VERSIONS.ALONZO: overincrement_opcert_file = cluster.gen_node_operational_cert( node_name=f"{node_name}_overincrement_opcert_file", kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=cold_counter_file, kes_period=cluster.get_kes_period(), ) # copy the new certificate and restart the node shutil.copy(overincrement_opcert_file, opcert_file) cluster_nodes.restart_nodes([node_name]) if invalid_opcert_epoch == 3: # check kes-period-info with operational certificate with # invalid counter # TODO: the query is currently broken, implement once it is fixed pass # in Babbage we'll use the original counter for issuing new valid opcert so the counter # value of new valid opcert equals to counter value of the original opcert +1 if VERSIONS.cluster_era > VERSIONS.ALONZO: shutil.copy(cold_counter_file_orig, cold_counter_file) # generate new operational certificate with valid `--kes-period` valid_opcert_file = cluster.gen_node_operational_cert( node_name=f"{node_name}_valid_opcert_file", kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=cold_counter_file, kes_period=cluster.get_kes_period(), ) # copy the new certificate and restart the node shutil.copy(valid_opcert_file, opcert_file) cluster_nodes.restart_nodes([node_name]) this_epoch = cluster.wait_for_new_epoch() LOGGER.info("Checking blocks production for another 2 epochs.") blocks_made_db = [] active_again_epoch = this_epoch for __ in range(2): _wait_epoch_chores( cluster_obj=cluster, temp_template=temp_template, this_epoch=this_epoch ) this_epoch = cluster.get_epoch() # check that the pool is producing blocks blocks_made = clusterlib_utils.get_ledger_state(cluster_obj=cluster)[ "blocksCurrent" ] blocks_made_db.append(stake_pool_id_dec in blocks_made) assert any(blocks_made_db), ( f"The pool '{pool_name}' has not produced any blocks " f"since epoch {active_again_epoch}" ) if kes_query_currently_broken: pytest.xfail("`query kes-period-info` is currently broken") else: # check kes-period-info with valid operational certificate kes_period_info = cluster.get_kes_period_info(valid_opcert_file) kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.ALL_VALID ) # check kes-period-info with invalid operational certificate, wrong counter and period kes_period_info = cluster.get_kes_period_info(invalid_opcert_file) kes.check_kes_period_info_result( kes_output=kes_period_info, expected_scenario=kes.KesScenarios.INVALID_KES_PERIOD if VERSIONS.cluster_era > VERSIONS.ALONZO else kes.KesScenarios.ALL_INVALID, ) @allure.link(helpers.get_vcs_link()) @pytest.mark.order(7) @pytest.mark.long def test_update_valid_opcert( self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, ): """Update a valid operational certificate with another valid operational certificate. * generate new operational certificate with valid `--kes-period` * copy new operational certificate to the node * stop the node so the corresponding pool is not minting new blocks * check `kes-period-info` while the pool is not minting blocks * start the node with the new operational certificate * check that the pool is minting blocks again * check that metrics reported by `kes-period-info` got updated once the pool started minting blocks again * check `kes-period-info` with the old (replaced) operational certificate """ # pylint: disable=too-many-statements pool_name = cluster_management.Resources.POOL2 node_name = "pool2" cluster = cluster_lock_pool2 temp_template = common.get_test_id(cluster) pool_rec = cluster_manager.cache.addrs_data[pool_name] node_cold = pool_rec["cold_key_pair"] stake_pool_id = cluster.get_stake_pool_id(node_cold.vkey_file) stake_pool_id_dec = helpers.decode_bech32(stake_pool_id) opcert_file = pool_rec["pool_operational_cert"] opcert_file_old = shutil.copy(opcert_file, f"{opcert_file}_old") with cluster_manager.restart_on_failure(): # generate new operational certificate with valid `--kes-period` new_opcert_file = cluster.gen_node_operational_cert( node_name=f"{node_name}_new_opcert_file", kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, cold_skey_file=pool_rec["cold_key_pair"].skey_file, cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_kes_period(), ) # copy new operational certificate to the node logfiles.add_ignore_rule( files_glob="*.stdout", regex="MuxBearerClosed", ignore_file_id=cluster_manager.worker_id, ) shutil.copy(new_opcert_file, opcert_file) # stop the node so the corresponding pool is not minting new blocks cluster_nodes.stop_nodes([node_name]) time.sleep(10) # check kes-period-info while the pool is not minting blocks # TODO: the query is currently broken kes_query_currently_broken = False try: kes_period_info_new = cluster.get_kes_period_info(opcert_file) except clusterlib.CLIError as err: if "currentlyBroken" not in str(err): raise kes_query_currently_broken = True if not kes_query_currently_broken: kes.check_kes_period_info_result( kes_output=kes_period_info_new, expected_scenario=kes.KesScenarios.ALL_VALID ) kes_period_info_old = cluster.get_kes_period_info(opcert_file_old) kes.check_kes_period_info_result( kes_output=kes_period_info_old, expected_scenario=kes.KesScenarios.ALL_VALID ) assert ( kes_period_info_new["metrics"]["qKesNodeStateOperationalCertificateNumber"] == kes_period_info_old["metrics"]["qKesNodeStateOperationalCertificateNumber"] ) # start the node with the new operational certificate cluster_nodes.start_nodes([node_name]) # make sure we are not at the very end of an epoch so we still have time for # the first block production check clusterlib_utils.wait_for_epoch_interval(cluster_obj=cluster, start=5, stop=-18) LOGGER.info("Checking blocks production for 5 epochs.") blocks_made_db = [] this_epoch = -1 updated_epoch = cluster.get_epoch() for __ in range(5): # wait for next epoch if cluster.get_epoch() == this_epoch: cluster.wait_for_new_epoch() # wait for the end of the epoch clusterlib_utils.wait_for_epoch_interval( cluster_obj=cluster, start=-19, stop=-15, force_epoch=True ) this_epoch = cluster.get_epoch() ledger_state = clusterlib_utils.get_ledger_state(cluster_obj=cluster) # save ledger state clusterlib_utils.save_ledger_state( cluster_obj=cluster, state_name=f"{temp_template}_{this_epoch}", ledger_state=ledger_state, ) # check that the pool is minting blocks blocks_made = ledger_state["blocksCurrent"] blocks_made_db.append(stake_pool_id_dec in blocks_made) assert any( blocks_made_db ), f"The pool '{pool_name}' has not minted any blocks since epoch {updated_epoch}" if kes_query_currently_broken: pytest.xfail("`query kes-period-info` is currently broken") else: # check that metrics reported by kes-period-info got updated once the pool started # minting blocks again kes_period_info_updated = cluster.get_kes_period_info(opcert_file) kes.check_kes_period_info_result( kes_output=kes_period_info_updated, expected_scenario=kes.KesScenarios.ALL_VALID ) assert ( kes_period_info_updated["metrics"]["qKesNodeStateOperationalCertificateNumber"] != kes_period_info_old["metrics"]["qKesNodeStateOperationalCertificateNumber"] ) # check kes-period-info with operational certificate with a wrong counter kes_period_info_invalid = cluster.get_kes_period_info(opcert_file_old) kes.check_kes_period_info_result( kes_output=kes_period_info_invalid, expected_scenario=kes.KesScenarios.INVALID_COUNTERS, ) @allure.link(helpers.get_vcs_link()) def test_no_kes_period_arg( self, cluster: clusterlib.ClusterLib, cluster_manager: cluster_management.ClusterManager, ): """Try to generate new operational certificate without specifying the `--kes-period`. Expect failure. """ pool_name = cluster_management.Resources.POOL2 pool_rec = cluster_manager.cache.addrs_data[pool_name] temp_template = common.get_test_id(cluster) out_file = Path(f"{temp_template}_shouldnt_exist.opcert") # try to generate new operational certificate without specifying the `--kes-period` with pytest.raises(clusterlib.CLIError) as excinfo: cluster.cli( [ "node", "issue-op-cert", "--kes-verification-key-file", str(pool_rec["kes_key_pair"].vkey_file), "--cold-signing-key-file", str(pool_rec["cold_key_pair"].skey_file), "--operational-certificate-issue-counter", str(pool_rec["cold_key_pair"].counter_file), "--out-file", str(out_file), ] ) assert "Missing: --kes-period NATURAL" in str(excinfo.value) assert not out_file.exists(), "New operational certificate was generated"