def test_transfer_to_rewards(self, cluster: clusterlib.ClusterLib, pool_user: clusterlib.PoolUser): """Send funds from the treasury pot to the reserves pot. Expected to fail until Alonzo. """ temp_template = helpers.get_func_name() amount = 50_000 mir_cert = cluster.gen_mir_cert_to_rewards(transfer=amount, tx_name=temp_template) tx_files = clusterlib.TxFiles( certificate_files=[mir_cert], signing_key_files=[ pool_user.payment.skey_file, *cluster.genesis_keys.delegate_skeys ], ) # send the transaction at the beginning of an epoch if cluster.time_from_epoch_start() > (cluster.epoch_length_sec // 6): cluster.wait_for_new_epoch() # fail is expected until Alonzo with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx( src_address=pool_user.payment.address, tx_name=temp_template, tx_files=tx_files, ) assert "MIRTransferNotCurrentlyAllowed" in str(excinfo.value)
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)
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"
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"
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"
def test_multisig_atleast_low_num_of_skeys( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Try to send funds from script address using the *atLeast* script. Num of skeys < required. Expect failure. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] skeys_len = len(payment_skey_files) required = skeys_len - 4 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.AT_LEAST, payment_vkey_files=payment_vkey_files, required=required, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address num_of_skeys = random.randrange(required, skeys_len) multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=payment_addrs[0].address, dst_address=script_addr, amount=300_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address, use lower number of skeys then required for num_of_skeys in range(required): with pytest.raises(clusterlib.CLIError) as excinfo: multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=script_addr, dst_address=payment_addrs[0].address, amount=1000, multisig_script=multisig_script, payment_skey_files=random.sample(payment_skey_files, k=num_of_skeys), script_is_src=True, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value)
def test_after_past(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Check that it's NOT possible to spend from the script address. The "after" slot is in the past. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] after_slot = cluster.get_tip()["slotNo"] - 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, slot=after_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.AFTER, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_to", src_address=payment_addrs[0].address, dst_address=script_addr, amount=500_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address - valid slot, # invalid range - `invalid_hereafter` is in the past with pytest.raises(clusterlib.CLIError) as excinfo: multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_from", src_address=script_addr, dst_address=payment_addrs[0].address, amount=10, multisig_script=multisig_script, payment_skey_files=payment_skey_files, invalid_before=1, invalid_hereafter=after_slot, script_is_src=True, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value)
def test_normal_tx_from_script_addr( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Try to send funds from script address using TX signed with skeys. Sending funds from script address is expected to fail when not using witness files. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_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, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=payment_addrs[0].address, dst_address=script_addr, amount=300_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address destinations_from = [ clusterlib.TxOut(address=payment_addrs[0].address, amount=1000) ] tx_files_from = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) # cannot send the TX without signing it using witness files with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_funds( src_address=script_addr, tx_name=temp_template, destinations=destinations_from, tx_files=tx_files_from, ) assert "MissingScriptWitnessesUTXOW" in str(excinfo.value)
def test_exceed_pay_stake_addr_from( self, cluster_pots: clusterlib.ClusterLib, registered_user: clusterlib.PoolUser, fund_src: str, ): """Try to send more funds than available from the reserves or treasury pot to stake address. Expect failure. * generate an MIR certificate * submit a TX with the MIR certificate * check that submitting the transaction fails with an expected error """ temp_template = helpers.get_func_name() cluster = cluster_pots amount = 30_000_000_000_000_000 init_balance = cluster.get_address_balance(registered_user.payment.address) mir_cert = cluster.gen_mir_cert_stake_addr( stake_addr=registered_user.stake.address, reward=amount, tx_name=temp_template, use_treasury=fund_src == self.TREASURY, ) tx_files = clusterlib.TxFiles( certificate_files=[mir_cert], signing_key_files=[ registered_user.payment.skey_file, *cluster.genesis_keys.delegate_skeys, ], ) # send the transaction at the beginning of an epoch if cluster.time_from_epoch_start() > (cluster.epoch_length_sec // 6): cluster.wait_for_new_epoch() with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx( src_address=registered_user.payment.address, tx_name=temp_template, tx_files=tx_files, ) assert "InsufficientForInstantaneousRewardsDELEG" in str(excinfo.value) assert ( cluster.get_address_balance(registered_user.payment.address) == init_balance ), f"Incorrect balance for source address `{registered_user.payment.address}`"
def test_multisig_atleast(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send funds to and from script address using the *atLeast* script.""" temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] skeys_len = len(payment_skey_files) required = skeys_len - 4 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.AT_LEAST, payment_vkey_files=payment_vkey_files, required=required, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address num_of_skeys = random.randrange(required, skeys_len) multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_to", src_address=payment_addrs[0].address, dst_address=script_addr, amount=2_000_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address for i in range(5): num_of_skeys = random.randrange(required, skeys_len) multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_from_{i}", src_address=script_addr, dst_address=payment_addrs[0].address, amount=1000, multisig_script=multisig_script, payment_skey_files=random.sample(payment_skey_files, k=num_of_skeys), script_is_src=True, )
def test_normal_tx_to_script_addr( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send funds to script address using TX signed with skeys (not using witness files).""" temp_template = helpers.get_func_name() src_address = payment_addrs[0].address amount = 1000 # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.ALL, payment_vkey_files=[p.vkey_file for p in payment_addrs], ) # create script address script_address = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # record initial balances src_init_balance = cluster.get_address_balance(src_address) dst_init_balance = cluster.get_address_balance(script_address) # send funds to script address destinations = [ clusterlib.TxOut(address=script_address, amount=amount) ] tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file]) tx_raw_output = cluster.send_funds( src_address=src_address, tx_name=temp_template, destinations=destinations, tx_files=tx_files, ) cluster.wait_for_new_block(new_blocks=2) # check final balances assert ( cluster.get_address_balance(src_address) == src_init_balance - tx_raw_output.fee - amount), f"Incorrect balance for source address `{src_address}`" assert ( cluster.get_address_balance(script_address) == dst_init_balance + amount ), f"Incorrect balance for destination address `{script_address}`"
def test_multisig_any_unlisted_skey( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Try to send funds from script address using the *any* script with unlisted skey. Expect failure. """ temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs[:-1]] payment_skey_files = [p.skey_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, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=payment_addrs[0].address, dst_address=script_addr, amount=300_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address, use skey that is not listed in the script with pytest.raises(clusterlib.CLIError) as excinfo: multisig_tx( cluster_obj=cluster, temp_template=temp_template, src_address=script_addr, dst_address=payment_addrs[0].address, amount=1000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[-1]], script_is_src=True, ) assert "ScriptWitnessNotValidatingUTXOW" in str(excinfo.value)
def test_script_before(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Check that it is possible to spend from script address before given slot.""" temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] before_slot = cluster.get_tip()["slotNo"] + 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, slot=before_slot, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_to", src_address=payment_addrs[0].address, dst_address=script_addr, amount=2_000_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_from", src_address=script_addr, dst_address=payment_addrs[0].address, amount=1000, multisig_script=multisig_script, payment_skey_files=payment_skey_files, invalid_before=100, invalid_hereafter=cluster.get_tip()["slotNo"] + 1000, script_is_src=True, )
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"
def test_tx_script_invalid(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Build transaction with invalid auxiliary script. Expect failure. """ temp_template = helpers.get_func_name() tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], script_files=[self.JSON_METADATA_FILE], # not valid script file ) with pytest.raises(clusterlib.CLIError) as excinfo: cluster.send_tx(src_address=payment_addrs[0].address, tx_name=temp_template, tx_files=tx_files) assert 'Error in $: key "type" not found' in str(excinfo.value)
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"
def test_multisig_no_required_atleast( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send funds from script address using the *atLeast* script with no required witnesses.""" temp_template = helpers.get_func_name() payment_vkey_files = [p.vkey_file for p in payment_addrs] payment_skey_files = [p.skey_file for p in payment_addrs] # create multisig script multisig_script = cluster.build_multisig_script( script_name=temp_template, script_type_arg=clusterlib.MultiSigTypeArgs.AT_LEAST, payment_vkey_files=payment_vkey_files, required=0, ) # create script address script_address = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # send funds to script address multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_to", src_address=payment_addrs[0].address, dst_address=script_address, amount=2_000_000, multisig_script=multisig_script, payment_skey_files=[payment_skey_files[0]], ) # send funds from script address multisig_tx( cluster_obj=cluster, temp_template=f"{temp_template}_from", src_address=script_address, dst_address=payment_addrs[0].address, amount=1000, multisig_script=multisig_script, payment_skey_files=[], script_is_src=True, )
def test_tx_script_metadata_cbor( self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Send transaction with auxiliary script and metadata CBOR. 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.AT_LEAST, payment_vkey_files=payment_vkey_files, required=2, slot=1000, slot_type_arg=clusterlib.MultiSlotTypeArgs.BEFORE, ) tx_files = clusterlib.TxFiles( signing_key_files=[payment_addrs[0].skey_file], metadata_cbor_files=[self.CBOR_METADATA_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 test_script_addr_length(self, cluster: clusterlib.ClusterLib, payment_addrs: List[clusterlib.AddressRecord]): """Check that script address length is the same as lenght of other addresses. There was an issue that script address was 32 bytes instead of 28 bytes. """ 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.ALL, payment_vkey_files=payment_vkey_files, ) # create script address script_addr = cluster.gen_script_addr(addr_name=temp_template, script_file=multisig_script) # check script address length assert len(script_addr) == len(payment_addrs[0].address)
def test_update_valid_opcert( self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: parallel_run.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, node_kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, node_cold_skey_file=pool_rec["cold_key_pair"].skey_file, node_cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_last_block_kes_period(), ) # restart the node with the new operational certificate shutil.copy(new_opcert_file, opcert_file) devops_cluster.restart_node(node_name) LOGGER.info("Checking blocks production for 5 epochs.") this_epoch = -1 for __ in range(5): # wait for next epoch if cluster.get_last_block_epoch() == this_epoch: cluster.wait_for_new_epoch() # wait for the end of the epoch time.sleep(clusterlib_utils.time_to_next_epoch_start(cluster) - 5) this_epoch = cluster.get_last_block_epoch() # save ledger state clusterlib_utils.save_ledger_state( cluster_obj=cluster, name_template=f"{temp_template}_{this_epoch}", ) # check that the pool is still producing blocks blocks_made = cluster.get_ledger_state()["nesBcur"]["unBlocksMade"] if blocks_made: assert ( stake_pool_id_dec in blocks_made ), f"The pool '{pool_name}' has not produced blocks in epoch {this_epoch}"
def test_opcert_past_kes_period( self, cluster_lock_pool2: clusterlib.ClusterLib, cluster_manager: parallel_run.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 """ 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_last_block_epoch() == this_epoch: cluster.wait_for_new_epoch() # wait for the end of the epoch time.sleep(clusterlib_utils.time_to_next_epoch_start(cluster) - 5) # save ledger state clusterlib_utils.save_ledger_state( cluster_obj=cluster, name_template=f"{temp_template}_{cluster.get_last_block_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, node_kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, node_cold_skey_file=pool_rec["cold_key_pair"].skey_file, node_cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_last_block_kes_period() - 1, ) expected_errors = [ (f"{node_name}.stdout", "TPraosCannotForgeKeyNotUsableYet"), ] with logfiles.expect_errors(expected_errors): # restart the node with the new operational certificate shutil.copy(invalid_opcert_file, opcert_file) devops_cluster.restart_node(node_name) LOGGER.info("Checking blocks production for 5 epochs.") this_epoch = -1 for __ in range(5): _wait_epoch_chores(this_epoch) this_epoch = cluster.get_last_block_epoch() # check that the pool is not producing any blocks blocks_made = cluster.get_ledger_state()["nesBcur"]["unBlocksMade"] 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, node_kes_vkey_file=pool_rec["kes_key_pair"].vkey_file, node_cold_skey_file=pool_rec["cold_key_pair"].skey_file, node_cold_counter_file=pool_rec["cold_key_pair"].counter_file, kes_period=cluster.get_last_block_kes_period(), ) # copy the new certificate and restart the node shutil.move(str(valid_opcert_file), str(opcert_file)) devops_cluster.restart_node(node_name) LOGGER.info("Checking blocks production for another 3 epochs.") for __ in range(5): _wait_epoch_chores(this_epoch) this_epoch = cluster.get_last_block_epoch() # check that the pool is not producing any blocks blocks_made = cluster.get_ledger_state()["nesBcur"]["unBlocksMade"] assert ( stake_pool_id_dec in blocks_made ), f"The pool '{pool_name}' has not produced blocks in epoch {this_epoch}"
def test_build_pay_stake_addr_from( self, cluster_pots: clusterlib.ClusterLib, registered_user: clusterlib.PoolUser, fund_src: str, ): """Send funds from the reserves or treasury pot to stake address. Uses `cardano-cli transaction build` command for building the transactions. * generate an MIR certificate * submit a TX with the MIR certificate * check that the expected amount was added to the stake address reward account """ temp_template = helpers.get_func_name() cluster = cluster_pots amount = 50_000_000 init_reward = cluster.get_stake_addr_info( registered_user.stake.address ).reward_account_balance init_balance = cluster.get_address_balance(registered_user.payment.address) mir_cert = cluster.gen_mir_cert_stake_addr( stake_addr=registered_user.stake.address, reward=amount, tx_name=temp_template, use_treasury=fund_src == self.TREASURY, ) tx_files = clusterlib.TxFiles( certificate_files=[mir_cert], signing_key_files=[ registered_user.payment.skey_file, *cluster.genesis_keys.delegate_skeys, ], ) # send the transaction at the beginning of an epoch if cluster.time_from_epoch_start() > (cluster.epoch_length_sec // 6): cluster.wait_for_new_epoch() tx_output = cluster.build_tx( src_address=registered_user.payment.address, tx_name=temp_template, tx_files=tx_files, fee_buffer=1000_000, witness_override=2, ) tx_signed = cluster.sign_tx( tx_body_file=tx_output.out_file, signing_key_files=tx_files.signing_key_files, tx_name=temp_template, ) cluster.submit_tx(tx_file=tx_signed, txins=tx_output.txins) assert ( cluster.get_address_balance(registered_user.payment.address) < init_balance ), f"Incorrect balance for source address `{registered_user.payment.address}`" cluster.wait_for_new_epoch() assert ( cluster.get_stake_addr_info(registered_user.stake.address).reward_account_balance == init_reward + amount ), f"Incorrect reward balance for stake address `{registered_user.stake.address}`" tx_db_record = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_output) if tx_db_record: stash_record = ( tx_db_record.treasury[0] if fund_src == self.TREASURY else tx_db_record.reserve[0] ) assert stash_record.amount == amount, ( "Incorrect amount transferred using MIR certificate " f"({stash_record.amount} != {amount})" ) assert stash_record.address == registered_user.stake.address, ( "Incorrect stake address " f"({stash_record.address} != {registered_user.stake.address})" )
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}")
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"
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"
def test_pay_unregistered_stake_addr_from( self, cluster_pots: clusterlib.ClusterLib, pool_users: List[clusterlib.PoolUser], fund_src: str, ): """Send funds from the reserves or treasury pot to unregistered stake address. * generate an MIR certificate * submit a TX with the MIR certificate * check that the amount was NOT added to the stake address reward account """ temp_template = helpers.get_func_name() cluster = cluster_pots pool_user = pool_users[0] if fund_src == self.TREASURY: amount = 1_500_000_000_000 else: amount = 50_000_000_000_000 init_balance = cluster.get_address_balance(pool_user.payment.address) mir_cert = cluster.gen_mir_cert_stake_addr( stake_addr=pool_user.stake.address, reward=amount, tx_name=temp_template, use_treasury=fund_src == self.TREASURY, ) tx_files = clusterlib.TxFiles( certificate_files=[mir_cert], signing_key_files=[ pool_user.payment.skey_file, *cluster.genesis_keys.delegate_skeys, ], ) # send the transaction at the beginning of an epoch if cluster.time_from_epoch_start() > (cluster.epoch_length_sec // 6): cluster.wait_for_new_epoch() tx_raw_output = cluster.send_tx( src_address=pool_user.payment.address, tx_name=temp_template, tx_files=tx_files, ) tx_epoch = cluster.get_epoch() assert ( cluster.get_address_balance(pool_user.payment.address) == init_balance - tx_raw_output.fee ), f"Incorrect balance for source address `{pool_user.payment.address}`" tx_db_record = dbsync_utils.check_tx(cluster_obj=cluster, tx_raw_output=tx_raw_output) if tx_db_record: if fund_src == self.TREASURY: assert tx_db_record.treasury[0].amount == amount, ( "Incorrect amount transferred from treasury " f"({tx_db_record.treasury[0].amount} != {amount})" ) else: assert tx_db_record.reserve[0].amount == amount, ( "Incorrect amount transferred from reserve " f"({tx_db_record.reserve[0].amount} != {amount})" ) cluster.wait_for_new_epoch() assert not cluster.get_stake_addr_info( pool_user.stake.address ).reward_account_balance, ( f"Reward was added for unregistered stake address `{pool_user.stake.address}`" ) if tx_db_record: # check that the amount was not transferred out of the pot pots_records = list(dbsync_utils.query_ada_pots(epoch_from=tx_epoch)) if fund_src == self.TREASURY: # normally `treasury[-1]` > `treasury[-2]` assert abs(pots_records[-1].treasury - pots_records[-2].treasury) < amount else: # normally `reserves[-1]` < `reserves[-2]` assert abs(pots_records[-2].reserves - pots_records[-1].reserves) < amount
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}")
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"
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}`"
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"