예제 #1
0
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
예제 #2
0
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()
예제 #3
0
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)
예제 #4
0
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
예제 #6
0
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)
예제 #7
0
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",
                )
            ],
        )
예제 #9
0
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())
예제 #10
0
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())
예제 #11
0
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",
                )
            ],
        )
예제 #12
0
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"
예제 #13
0
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
예제 #16
0
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"
예제 #18
0
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"
예제 #19
0
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)
예제 #20
0
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
예제 #21
0
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)
예제 #22
0
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"
예제 #23
0
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)
예제 #24
0
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"
예제 #26
0
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}'!"
예제 #27
0
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}`"
예제 #28
0
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,
        )
예제 #29
0
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"
예제 #30
0
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"