def return_funds_to_faucet( *src_addrs: clusterlib.AddressRecord, cluster_obj: clusterlib.ClusterLib, faucet_addr: str, amount: int = -1, tx_name: Optional[str] = None, destination_dir: FileType = ".", ) -> None: """Send `amount` from all `src_addrs` to `faucet_addr`. The amount of "-1" means all available funds. """ tx_name = tx_name or helpers.get_timestamped_rand_str() tx_name = f"{tx_name}_return_funds" with helpers.FileLockIfXdist( f"{helpers.get_basetemp()}/{faucet_addr}.lock"): try: logging.disable(logging.ERROR) for src in src_addrs: fund_dst = [ clusterlib.TxOut(address=faucet_addr, amount=amount) ] fund_tx_files = clusterlib.TxFiles( signing_key_files=[src.skey_file]) # try to return funds; don't mind if there's not enough funds for fees etc. try: cluster_obj.send_funds( src_address=src.address, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, destination_dir=destination_dir, ) except Exception: pass finally: logging.disable(logging.NOTSET)
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)
def return_funds_to_faucet( cluster_obj: clusterlib.ClusterLib, src_addr: clusterlib.AddressRecord, faucet_addr: str, tx_name: str, ) -> None: """Send funds from `src_addr` to `faucet_addr`.""" tx_name = f"rf_{tx_name}_return_funds" # the amount of "-1" means all available funds. fund_dst = [clusterlib.TxOut(address=faucet_addr, amount=-1)] fund_tx_files = clusterlib.TxFiles(signing_key_files=[src_addr.skey_file]) LOGGER.info(f"Returning funds from '{src_addr.address}'") # try to return funds; don't mind if there's not enough funds for fees etc. try: cluster_obj.send_funds( src_address=src_addr.address, destinations=fund_dst, tx_name=tx_name, tx_files=fund_tx_files, verify_tx=False, ) except clusterlib.CLIError: pass
def test_tx_script_no_metadata( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send transaction with auxiliary script and no other metadata. Check that the auxiliary script is present in the TX body. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ANY, payment_vkey_files=payment_vkey_files, ) tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], script_files=[multisig_script], ) tx_raw_output = cluster.send_tx(src_address=payment_addrs[0].address, tx_name=temp_template, tx_files=tx_files) cluster.wait_for_new_block(new_blocks=2) assert tx_raw_output.fee, "Transaction had no fee" with open(tx_raw_output.out_file) as body_fp: tx_body_json = json.load(body_fp) cbor_body = bytes.fromhex(tx_body_json["cborHex"]) cbor_body_metadata = cbor2.loads(cbor_body)[1] cbor_body_script = cbor_body_metadata[1] assert cbor_body_script, "Auxiliary script not present"
def deregister_stake_addr(cluster_obj: clusterlib.ClusterLib, pool_user: clusterlib.PoolUser, name_template: str) -> None: """Deregister stake address.""" # files for deregistering stake address stake_addr_dereg_cert = cluster_obj.gen_stake_addr_deregistration_cert( addr_name=f"rf_{name_template}_addr0_dereg", stake_vkey_file=pool_user.stake.vkey_file) tx_files_deregister = clusterlib.TxFiles( certificate_files=[stake_addr_dereg_cert], signing_key_files=[ pool_user.payment.skey_file, pool_user.stake.skey_file ], ) LOGGER.info(f"Deregistering stake address '{pool_user.stake.address}'") try: cluster_obj.send_tx( src_address=pool_user.payment.address, tx_name=f"{name_template}_dereg_stake_addr", tx_files=tx_files_deregister, ) except clusterlib.CLIError: return
def withdraw_reward( cluster_obj: clusterlib.ClusterLib, stake_addr_record: clusterlib.AddressRecord, dst_addr_record: clusterlib.AddressRecord, name_template: str, ) -> None: """Withdraw rewards to payment address.""" dst_address = dst_addr_record.address tx_files_withdrawal = clusterlib.TxFiles( signing_key_files=[dst_addr_record.skey_file, stake_addr_record.skey_file], ) LOGGER.info(f"Withdrawing rewards for '{stake_addr_record.address}'") try: cluster_obj.send_tx( src_address=dst_address, tx_name=f"rf_{name_template}_reward_withdrawal", tx_files=tx_files_withdrawal, withdrawals=[clusterlib.TxOut(address=stake_addr_record.address, amount=-1)], ) except clusterlib.CLIError: return cluster_obj.wait_for_new_block(new_blocks=2)
def create_addrs_data( self, cluster_obj: clusterlib.ClusterLib, destination_dir: FileType = ".") -> Dict[str, Dict[str, Any]]: """Create addresses and their keys for usage in tests.""" destination_dir = Path(destination_dir).expanduser() destination_dir.mkdir(parents=True, exist_ok=True) cluster_env = get_cluster_env() instance_num = cluster_env.instance_num addrs_data: Dict[str, Dict[str, Any]] = {} for addr_name in self.test_addr_records: addr_name_instance = f"{addr_name}_ci{instance_num}" payment = cluster_obj.gen_payment_addr_and_keys( name=addr_name_instance, destination_dir=destination_dir, ) addrs_data[addr_name] = { "payment": payment, } LOGGER.debug("Funding created addresses.") # update `addrs_data` with byron addresses byron_dir = get_cluster_env().state_dir / "byron" for b in range(len(list(byron_dir.glob("*.skey")))): byron_addr = { "payment": clusterlib.AddressRecord( address=clusterlib.read_address_from_file( byron_dir / f"address-00{b}-converted"), vkey_file=byron_dir / f"payment-keys.00{b}-converted.vkey", skey_file=byron_dir / f"payment-keys.00{b}-converted.skey", ) } addrs_data[f"byron00{b}"] = byron_addr # fund from converted byron address to_fund = [d["payment"] for d in addrs_data.values()] clusterlib_utils.fund_from_faucet( *to_fund, cluster_obj=cluster_obj, faucet_data=addrs_data["byron000"], amount=6_000_000_000_000, destination_dir=destination_dir, force=True, ) return addrs_data
def _tx_scripts_hashes( cluster_obj: clusterlib.ClusterLib, records: Union[clusterlib.OptionalScriptTxIn, clusterlib.OptionalMint], ) -> Dict[str, Union[clusterlib.OptionalScriptTxIn, clusterlib.OptionalMint]]: """Create a hash table of Tx Plutus data indexed by script hash.""" hashes_db: dict = {} for r in records: shash = cluster_obj.get_policyid(script_file=r.script_file) shash_rec = hashes_db.get(shash) if shash_rec is None: hashes_db[shash] = [r] continue shash_rec.append(r) return hashes_db
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
def load_pools_data(cluster_obj: clusterlib.ClusterLib) -> dict: """Load data for pools existing in the cluster environment.""" data_dir = get_cluster_env().state_dir / "nodes" pools_data = {} for pool_data_dir in data_dir.glob("node-pool*"): pools_data[pool_data_dir.name] = { "payment": clusterlib.AddressRecord( address=clusterlib.read_address_from_file(pool_data_dir / "owner.addr"), vkey_file=pool_data_dir / "owner-utxo.vkey", skey_file=pool_data_dir / "owner-utxo.skey", ), "stake": clusterlib.AddressRecord( address=clusterlib.read_address_from_file(pool_data_dir / "owner-stake.addr"), vkey_file=pool_data_dir / "owner-stake.vkey", skey_file=pool_data_dir / "owner-stake.skey", ), "reward": clusterlib.AddressRecord( address=cluster_obj.gen_stake_addr( addr_name="reward", stake_vkey_file=pool_data_dir / "reward.vkey", destination_dir=pool_data_dir, ), vkey_file=pool_data_dir / "reward.vkey", skey_file=pool_data_dir / "reward.skey", ), "stake_addr_registration_cert": pool_data_dir / "stake.reg.cert", "stake_addr_delegation_cert": pool_data_dir / "owner-stake.deleg.cert", "reward_addr_registration_cert": pool_data_dir / "stake-reward.reg.cert", "pool_registration_cert": pool_data_dir / "register.cert", "pool_operational_cert": pool_data_dir / "op.cert", "cold_key_pair": clusterlib.ColdKeyPair( vkey_file=pool_data_dir / "cold.vkey", skey_file=pool_data_dir / "cold.skey", counter_file=pool_data_dir / "cold.counter", ), "vrf_key_pair": clusterlib.KeyPair( vkey_file=pool_data_dir / "vrf.vkey", skey_file=pool_data_dir / "vrf.skey", ), "kes_key_pair": clusterlib.KeyPair( vkey_file=pool_data_dir / "kes.vkey", skey_file=pool_data_dir / "kes.skey", ), } return pools_data
def create_payment_addr_records( *names: str, cluster_obj: clusterlib.ClusterLib, stake_vkey_file: Optional[FileType] = None, destination_dir: FileType = ".", ) -> List[clusterlib.AddressRecord]: """Create new payment address(es).""" addrs = [ cluster_obj.gen_payment_addr_and_keys( name=name, stake_vkey_file=stake_vkey_file, destination_dir=destination_dir, ) for name in names ] LOGGER.debug(f"Created {len(addrs)} payment address(es)") return addrs
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
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)
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}`"
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
def update_params_build( cluster_obj: clusterlib.ClusterLib, src_addr_record: clusterlib.AddressRecord, update_proposals: List[UpdateProposal], ) -> None: """Update params using update proposal. Uses `cardano-cli transaction build` command for building the transactions. """ if not update_proposals: return _cli_args = [(u.arg, str(u.value)) for u in update_proposals] cli_args = list(itertools.chain.from_iterable(_cli_args)) temp_template = helpers.get_timestamped_rand_str() # assumption is update proposals are submitted near beginning of epoch epoch = cluster_obj.get_epoch() out_file = cluster_obj.gen_update_proposal( cli_args=cli_args, epoch=epoch, tx_name=temp_template, ) tx_files = clusterlib.TxFiles( proposal_files=[out_file], signing_key_files=[ *cluster_obj.genesis_keys.delegate_skeys, Path(src_addr_record.skey_file), ], ) tx_output = cluster_obj.build_tx( src_address=src_addr_record.address, tx_name=f"{temp_template}_submit_proposal", tx_files=tx_files, fee_buffer=2000_000, ) tx_signed = cluster_obj.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_submit_proposal", ) cluster_obj.submit_tx(tx_file=tx_signed, txins=tx_output.txins) LOGGER.info(f"Update Proposal submitted ({cli_args})")
def _withdraw_rewards( *pool_users: clusterlib.PoolUser, cluster_obj: clusterlib.ClusterLib, tx_name: str, ) -> clusterlib.TxRawOutput: """Withdraw rewards from multiple stake addresses to corresponding payment addresses.""" src_addr = pool_users[0].payment tx_files_withdrawal = clusterlib.TxFiles(signing_key_files=[ src_addr.skey_file, *[p.stake.skey_file for p in pool_users] ], ) tx_raw_withdrawal_output = cluster_obj.send_tx( src_address=src_addr.address, tx_name=f"{tx_name}_reward_withdrawals", tx_files=tx_files_withdrawal, withdrawals=[ clusterlib.TxOut(address=p.stake.address, amount=-1) for p in pool_users ], ) return tx_raw_withdrawal_output
def test_shelley_cddl(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Check expected failure when Shelley Tx is used with CDDL format.""" temp_template = common.get_test_id(cluster) src_address = payment_addrs[0].address dst_address = payment_addrs[1].address # amount value -1 means all available funds destinations = [clusterlib.TxOut(address=dst_address, amount=-1)] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[1].skey_file]) fee = cluster.calculate_tx_fee( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, ) orig_cddl_value = cluster.use_cddl try: cluster.use_cddl = True tx_raw_output = cluster.build_raw_tx( src_address=src_address, tx_name=temp_template, txouts=destinations, tx_files=tx_files, fee=fee, ) finally: cluster.use_cddl = orig_cddl_value with pytest.raises(clusterlib.CLIError) as excinfo: cluster.sign_tx( tx_body_file=tx_raw_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=temp_template, ) if "TextEnvelope error" in str(excinfo.value): pytest.xfail("TextEnvelope error") else: pytest.fail(f"Unexpected error:\n{excinfo.value}")
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"
def check_tx(cluster_obj: clusterlib.ClusterLib, tx_raw_output: clusterlib.TxRawOutput, retry: bool = True) -> Optional[TxRecord]: """Check a transaction in db-sync.""" if not configuration.HAS_DBSYNC: return None txhash = cluster_obj.get_txid(tx_body_file=tx_raw_output.out_file) # under load it might be necessary to wait a bit and retry the query if retry: for r in range(3): if r > 0: LOGGER.warning( f"Repeating TX SQL query for '{txhash}' for the {r} time.") time.sleep(2) try: response = get_tx_record(txhash=txhash) break except RuntimeError: if r == 2: raise else: response = get_tx_record(txhash=txhash) txouts_amount = clusterlib_utils.get_amount(tx_raw_output.txouts) assert ( response.out_sum == txouts_amount ), f"Sum of TX amounts doesn't match ({response.out_sum} != {txouts_amount})" assert (response.fee == tx_raw_output.fee ), f"TX fee doesn't match ({response.fee} != {tx_raw_output.fee})" assert response.invalid_before == tx_raw_output.invalid_before, ( "TX invalid_before doesn't match " f"({response.invalid_before} != {tx_raw_output.invalid_before})") assert response.invalid_hereafter == tx_raw_output.invalid_hereafter, ( "TX invalid_hereafter doesn't match " f"({response.invalid_hereafter} != {tx_raw_output.invalid_hereafter})") len_db_txouts, len_out_txouts = len(response.txouts), len( tx_raw_output.txouts) assert ( len_db_txouts == len_out_txouts ), f"Number of TX outputs doesn't match ({len_db_txouts} != {len_out_txouts})" tx_txouts = sorted(tx_raw_output.txouts) db_txouts = sorted( clusterlib_utils.utxodata2txout(r) for r in response.txouts) assert tx_txouts == db_txouts, f"TX txouts don't match ({tx_txouts} != {db_txouts})" tx_txins = sorted(tx_raw_output.txins) db_txins = sorted(response.txins) assert tx_txins == db_txins, f"TX txins don't match ({tx_txins} != {db_txins})" # calculate minting amount sum for records with same address and token mint_txouts: Dict[str, clusterlib.TxOut] = {} for mt in tx_raw_output.mint: mt_id = f"{mt.address}_{mt.coin}" if mt_id in mint_txouts: mt_stored = mint_txouts[mt_id] mint_txouts[mt_id] = mt_stored._replace(amount=mt_stored.amount + mt.amount) else: mint_txouts[mt_id] = mt len_db_mint, len_out_mint = len(response.mint), len(mint_txouts.values()) assert ( len_db_mint == len_out_mint ), f"Number of MA minting doesn't match ({len_db_mint} != {len_out_mint})" return response
def wait_epochs(cluster: clusterlib.ClusterLib): """Make sure we are not checking metrics in epoch < 4.""" epochs_to_wait = 4 - cluster.get_epoch() if epochs_to_wait > 0: cluster.wait_for_new_epoch(new_epochs=epochs_to_wait)
def mint_or_burn_witness( cluster_obj: clusterlib.ClusterLib, new_tokens: List[TokenRecord], temp_template: str, invalid_hereafter: Optional[int] = None, invalid_before: Optional[int] = None, ) -> clusterlib.TxRawOutput: """Mint or burn tokens, depending on the `amount` value. Sign using witnesses. Positive `amount` value means minting, negative means burning. """ _issuers_addrs = [t.issuers_addrs for t in new_tokens] issuers_addrs = set(itertools.chain.from_iterable(_issuers_addrs)) issuers_skey_files = {p.skey_file for p in issuers_addrs} src_address = new_tokens[0].token_mint_addr.address # create TX body tx_files = clusterlib.TxFiles( script_files=clusterlib.ScriptFiles(minting_scripts=[t.script for t in new_tokens]), ) mint = [ clusterlib.TxOut(address=t.token_mint_addr.address, amount=t.amount, coin=t.token) for t in new_tokens ] fee = cluster_obj.calculate_tx_fee( src_address=src_address, tx_name=temp_template, tx_files=tx_files, mint=mint, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=len(issuers_skey_files), ) tx_raw_output = cluster_obj.build_raw_tx( src_address=src_address, tx_name=temp_template, tx_files=tx_files, fee=fee, invalid_hereafter=invalid_hereafter, invalid_before=invalid_before, mint=mint, ) # create witness file for each required key witness_files = [ cluster_obj.witness_tx( tx_body_file=tx_raw_output.out_file, witness_name=f"{temp_template}_skey{idx}", signing_key_files=[skey], ) for idx, skey in enumerate(issuers_skey_files) ] # sign TX using witness files tx_witnessed_file = cluster_obj.assemble_tx( tx_body_file=tx_raw_output.out_file, witness_files=witness_files, tx_name=temp_template, ) # submit signed TX cluster_obj.submit_tx(tx_file=tx_witnessed_file, txins=tx_raw_output.txins) return tx_raw_output
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
def _deploy_lobster_nft( cluster_obj: clusterlib.ClusterLib, temp_template: str, issuer_addr: clusterlib.AddressRecord, token_utxos: List[clusterlib.UTXOData], lobster_nft_token: str, nft_amount: int, lovelace_amount: int, ) -> Tuple[str, List[clusterlib.UTXOData], clusterlib.TxRawOutput]: """Deploy the LobsterNFT token to script address.""" script_address = cluster_obj.gen_payment_addr( addr_name=f"{temp_template}_deploy_nft", payment_script_file=LOBSTER_PLUTUS) tx_files = clusterlib.TxFiles(signing_key_files=[issuer_addr.skey_file], ) txouts = [ clusterlib.TxOut(address=script_address, amount=lovelace_amount, datum_hash=LOBSTER_DATUM_HASH), clusterlib.TxOut( address=script_address, amount=nft_amount, coin=lobster_nft_token, datum_hash=LOBSTER_DATUM_HASH, ), ] funds_txin = cluster_obj.get_utxo_with_highest_amount( address=issuer_addr.address) tx_output = cluster_obj.build_tx( src_address=issuer_addr.address, tx_name=f"{temp_template}_deploy_nft", tx_files=tx_files, txins=[*token_utxos, funds_txin], txouts=txouts, ) tx_signed = cluster_obj.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=f"{temp_template}_deploy_nft", ) cluster_obj.submit_tx(tx_file=tx_signed, txins=token_utxos) txid = cluster_obj.get_txid(tx_body_file=tx_output.out_file) deployed_token_utxos = cluster_obj.get_utxo(txin=f"{txid}#1") # check expected balances token_utxo_lovelace = [ u for u in deployed_token_utxos if u.coin == clusterlib.DEFAULT_COIN ][0] assert ( token_utxo_lovelace.amount == lovelace_amount ), f"Incorrect Lovelace balance for script address address `{script_address}`" token_utxo_lobster = [ u for u in deployed_token_utxos if u.coin == lobster_nft_token ][0] assert ( token_utxo_lobster.amount == nft_amount ), f"Incorrect token balance for token issuer address `{script_address}`" return script_address, deployed_token_utxos, tx_output
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)
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
def _fund_script( temp_template: str, cluster: clusterlib.ClusterLib, payment_addr: clusterlib.AddressRecord, dst_addr: clusterlib.AddressRecord, plutus_op: plutus_common.PlutusOp, amount: int, redeem_cost: plutus_common.ScriptCost, use_reference_script: Optional[bool] = False, use_inline_datum: Optional[bool] = False, ) -> Tuple[ List[clusterlib.UTXOData], List[clusterlib.UTXOData], List[clusterlib.UTXOData], clusterlib.TxRawOutput, ]: """Fund a Plutus script and create the locked UTxO, collateral UTxO and reference script.""" script_address = cluster.gen_payment_addr( addr_name=temp_template, payment_script_file=plutus_op.script_file ) # create a Tx output with a datum hash at the script address tx_files = clusterlib.TxFiles( signing_key_files=[payment_addr.skey_file], ) txouts = [ clusterlib.TxOut( address=script_address, amount=amount + redeem_cost.fee + FEE_REDEEM_TXSIZE, inline_datum_file=( plutus_op.datum_file if plutus_op.datum_file and use_inline_datum else "" ), inline_datum_value=( plutus_op.datum_value if plutus_op.datum_value and use_inline_datum else "" ), datum_hash_file=( plutus_op.datum_file if plutus_op.datum_file and not use_inline_datum else "" ), datum_hash_value=( plutus_op.datum_value if plutus_op.datum_value and not use_inline_datum else "" ), ), # for collateral clusterlib.TxOut(address=dst_addr.address, amount=redeem_cost.collateral), ] # for reference script if use_reference_script: txouts.append( clusterlib.TxOut( address=dst_addr.address, amount=amount, reference_script_file=plutus_op.script_file, ) ) tx_raw_output = cluster.send_tx( src_address=payment_addr.address, tx_name=f"{temp_template}_step1", txouts=txouts, tx_files=tx_files, # TODO: workaround for https://github.com/input-output-hk/cardano-node/issues/1892 witness_count_add=2, join_txouts=False, ) txid = cluster.get_txid(tx_body_file=tx_raw_output.out_file) script_utxos = cluster.get_utxo(txin=f"{txid}#0") assert script_utxos, "No script UTxO" collateral_utxos = cluster.get_utxo(txin=f"{txid}#1") assert collateral_utxos, "No collateral UTxO" reference_utxos = [] if use_reference_script: reference_utxos = cluster.get_utxo(txin=f"{txid}#2") assert reference_utxos, "No reference script UTxO" return script_utxos, collateral_utxos, reference_utxos, tx_raw_output
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
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
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