def do_test_recompress_storage(self, *, archive: bool) -> None: self.nodes = sanity.rpc_tx_status.start_cluster(archive=archive) # Give the network some time to generate a few blocks. The same goes # for other sleeps in this method. time.sleep(5) # Execute a few transactions sanity.rpc_tx_status.test_tx_status(self.nodes) time.sleep(5) # Recompress storage on each node for idx, node in enumerate(self.nodes): logger.info(f'Stopping node{idx}') node.kill(gentle=True) node_dir = pathlib.Path(node.node_dir) self._call(node, 'recompress-storage', '--output-dir=' + str(node_dir / 'data-new')) (node_dir / 'data').rename(node_dir / 'data-old') (node_dir / 'data-new').rename(node_dir / 'data') cmd = self._call(node, 'view-state', 'apply-range', '--start-index=0', '--verbose-output') # Restart all nodes with the new database for idx, node in enumerate(self.nodes): logger.info(f'Starting node{idx}') node.start() # Execute a few more transactions time.sleep(5) sanity.rpc_tx_status.test_tx_status(self.nodes, nonce_offset=3)
def get_up_to(from_, to): global first_epoch_switch_height, last_epoch for height, hash_ in utils.poll_blocks(nodes[0], timeout=TIMEOUT, poll_interval=0.01): block = nodes[0].get_block(hash_) hash_to_height[hash_] = height height_to_hash[height] = hash_ cur_epoch = block['result']['header']['epoch_id'] hash_to_epoch[hash_] = cur_epoch hash_to_next_epoch[hash_] = block['result']['header']['next_epoch_id'] if (first_epoch_switch_height is None and last_epoch is not None and last_epoch != cur_epoch): first_epoch_switch_height = height last_epoch = cur_epoch if height >= to: break for i in range(from_, to + 1): hash_ = height_to_hash[i] logger.info( f"{i} {hash_} {hash_to_epoch[hash_]} {hash_to_next_epoch[hash_]}") if len(epochs) == 0 or epochs[-1] != hash_to_epoch[hash_]: epochs.append(hash_to_epoch[hash_])
def get_metrics(node_name: str, node: cluster.BootNode) -> typing.Dict[str, int]: """Fetches partial encoded chunk request count metrics from node. Args: node_name: Node’s name used when logging the counters. This is purely for debugging. node: Node to fetch metrics from. Returns: A `{key: count}` dictionary where key is in ‘method/success’ format. The values correspond to the near_partial_encoded_chunk_request_processing_time_count Prometheus metric. """ url = 'http://{}:{}/metrics'.format(*node.rpc_addr()) response = requests.get(url) response.raise_for_status() metric_name = 'near_partial_encoded_chunk_request_processing_time' histogram = next( (metric for metric in prometheus_client.parser.text_string_to_metric_families( response.content.decode('utf8')) if metric.name == metric_name), None) if not histogram: return {} counts = dict((sample.labels['method'] + '/' + sample.labels['success'], int(sample.value)) for sample in histogram.samples if sample.name.endswith('_count')) logger.info(f'{node_name} counters: ' + '; '.join(f'{key}: {count}' for key, count in sorted(counts.items()))) return counts
def do_moar_stakes(last_block_hash, update_expected): global next_nonce, all_stakes, fake_stakes, sequence if len(sequence) == 0: stakes = [0, 0, 0] # have 1-2 validators with stake, and the remaining without # make numbers dibisable by 1M so that we can easily distinguish a situation when the current locked amt has some reward added to it (not divisable by 1M) vs not (divisable by 1M) stakes[random.randint(0, 2)] = random.randint( 70000000000000000000000000, 100000000000000000000000000) * 1000000 stakes[random.randint(0, 2)] = random.randint( 70000000000000000000000000, 100000000000000000000000000) * 1000000 else: stakes = sequence[0] sequence = sequence[1:] vals = get_validators() val_id = int(list(vals)[0][4:]) for i in range(3): tx = sign_staking_tx(nodes[i].signer_key, nodes[i].validator_key, stakes[i], next_nonce, base58.b58decode(last_block_hash.encode('utf8'))) nodes[val_id].send_tx(tx) next_nonce += 1 if update_expected: fake_stakes = [0, 0, 0] all_stakes.append(stakes) else: fake_stakes = stakes logger.info("Sent %s staking txs: %s" % ("REAL" if update_expected else "fake", stakes))
def __init__(self, key, init_nonce, base_block_hash, rpc_info=None, rpc_infos=None): """ `rpc_info` takes precedence over `rpc_infos` for compatibility. One of them must be set. If `rpc_info` is set, only this RPC node will be contacted. Otherwise if `rpc_infos` is set, an RPC node will be selected randomly from that set for each transaction attempt. """ self.key = key self.nonce = init_nonce self.base_block_hash = base_block_hash assert rpc_info or rpc_infos if rpc_info: assert not rpc_infos rpc_infos = [rpc_info] self.rpc_infos = rpc_infos assert key.account_id self.tx_timestamps = [] logger.info( f'Creating Account {key.account_id} {init_nonce} {self.rpc_infos[0]} {key.pk} {key.sk}' )
def start_node(node): m = node.machine logger.info(f'INFO: Starting node {m.name}') pid = get_near_pid(m) if pid == '': start_process = m.run('sudo -u ubuntu -i', input=TMUX_START_SCRIPT) assert start_process.returncode == 0, m.name + '\n' + start_process.stderr
def init_token2_account(account, i): s = f'{{"account_id": "{account.key.account_id}"}}' tx_res = mocknet_helpers.retry_and_ignore_errors( lambda: account.send_call_contract_raw_tx( mocknet.TOKEN2_ACCOUNT, 'storage_deposit', bytes(s, encoding='utf-8'), 1250000000000000000000)) # 0.00125 * 1e24) logger.info(f'Account {account.key.account_id} storage_deposit {tx_res}') s = f'{{"token_account_id": "{mocknet.TOKEN2_ACCOUNT}"}}' logger.info( f'Calling function "register_token" with arguments {s} on account {i}') tx_res = mocknet_helpers.retry_and_ignore_errors( lambda: account. send_call_contract_raw_tx(mocknet.SKYWARD_ACCOUNT, 'register_token', bytes(s, encoding='utf-8'), 0.01 * 1e24)) logger.info( f'Account {account.key.account_id} register_token token2 {tx_res}') # The next transaction depends on the previous transaction succeeded. # Sleeping for 1 second is the poor man's solution for waiting for that transaction to succeed. # This works because the contracts are being deployed slow enough to keep block production above 1 bps. mocknet_helpers.wait_at_least_one_block() s = f'{{"receiver_id": "{account.key.account_id}", "amount": "1000000000000000000"}}' logger.info( f'Calling function "ft_transfer" with arguments {s} on account {i}') tx_res = mocknet_helpers.retry_and_ignore_errors( get_token2_owner_account().send_call_contract_raw_tx( mocknet.TOKEN2_ACCOUNT, 'ft_transfer', bytes(s, encoding='utf-8'), 1)) logger.info( f'{get_token2_owner_account().key.account_id} ft_transfer to {account.key.account_id} {tx_res}' )
def check_slow_blocks(initial_metrics, final_metrics): delta = Metrics.diff(final_metrics, initial_metrics) slow_process_blocks = delta.block_processing_time[ 'le +Inf'] - delta.block_processing_time['le 1'] logger.info( f'Number of blocks processing for more than 1s: {slow_process_blocks}') return slow_process_blocks == 0
def get_test_accounts_from_args(argv): node_account_id = argv[1] pk = argv[2] sk = argv[3] rpc_nodes = argv[4].split(',') num_nodes = int(argv[5]) max_tps = float(argv[6]) logger.info(f'rpc_nodes: {rpc_nodes}') node_account_key = key.Key(node_account_id, pk, sk) test_account_keys = [ key.Key(mocknet.load_testing_account_id(node_account_id, i), pk, sk) for i in range(mocknet.NUM_ACCOUNTS) ] base_block_hash = mocknet_helpers.get_latest_block_hash() rpc_infos = [(rpc_addr, mocknet_helpers.RPC_PORT) for rpc_addr in rpc_nodes] node_account = account.Account(node_account_key, mocknet_helpers.get_nonce_for_pk( node_account_key.account_id, node_account_key.pk), base_block_hash, rpc_infos=rpc_infos) accounts = [ account.Account(key, mocknet_helpers.get_nonce_for_pk( key.account_id, key.pk), base_block_hash, rpc_infos=rpc_infos) for key in test_account_keys ] max_tps_per_node = max_tps / num_nodes return node_account, accounts, max_tps_per_node
def send_moar_txs(self, last_block_hash, num, use_routing): last_balances = [x for x in self.expected_balances] for i in range(num): while True: from_ = random.randint(0, self.num_nodes - 1) if self.nodes[from_] is not None: break to = random.randint(0, self.num_nodes - 2) if to >= from_: to += 1 amt = random.randint(0, 500) if self.expected_balances[from_] >= amt: logger.info("Sending a tx from %s to %s for %s" % (from_, to, amt)) tx = sign_payment_tx( self.nodes[from_].signer_key, 'test%s' % to, amt, self.next_nonce, base58.b58decode(last_block_hash.encode('utf8'))) if use_routing: self.nodes[0].send_tx(tx) else: self.nodes[self.act_to_val[from_]].send_tx(tx) self.expected_balances[from_] -= amt self.expected_balances[to] += amt self.next_nonce += 1
def collect_gcloud_config(num_nodes): tempdir = get_near_tempdir() keys = [] for i in range(num_nodes): node_dir = tempdir / f'node{i}' if not node_dir.exists(): # TODO: avoid hardcoding the username logger.info(f'downloading node{i} config from gcloud') node_dir.mkdir(parents=True, exist_ok=True) host = gcloud.get(f'pytest-node-{user_name()}-{i}') for filename in ('config.json', 'signer0_key.json', 'validator_key.json', 'node_key.json'): host.download(f'/home/bowen_nearprotocol_com/.near/{filename}', str(node_dir)) with open(node_dir / 'signer0_key.json') as f: key = json.load(f) keys.append(key) with open(tempdir / 'node0' / 'config.json') as f: config = json.load(f) ip_addresses = map(lambda x: x.split('@')[-1], config['network']['boot_nodes'].split(',')) res = { 'nodes': list(map(lambda x: { 'ip': x.split(':')[0], 'port': 3030 }, ip_addresses)), 'accounts': keys } outfile = tempdir / 'gcloud_config.json' with open(outfile, 'w') as f: json.dump(res, f) os.environ[cluster.CONFIG_ENV_VAR] = str(outfile)
def __download_binary(release: str, deploy: str) -> Executables: """Download binary for given release and deploy.""" logger.info(f'Getting neard for {release}@{_UNAME} (deploy={deploy})') neard = _OUT_DIR / f'neard-{release}-{deploy}' basehref = f'{_BASEHREF}/nearcore/{_UNAME}/{release}/{deploy}' __download_file_if_missing(neard, f'{basehref}/neard') return Executables(_OUT_DIR, neard)
def start_node(node, upgrade_schedule=None): m = node.machine logger.info(f'Starting node {m.name}') attempt = 0 success = False while attempt < 3: pid = get_near_pid(m) if pid != '': success = True break start_process = m.run('sudo -u ubuntu -i', input=neard_start_script( node, upgrade_schedule=upgrade_schedule, epoch_height=0)) if start_process.returncode == 0: success = True break logger.warn( f'Failed to start process, returncode: {start_process.returncode}\n{node.instance_name}\n{start_process.stderr}' ) attempt += 1 time.sleep(1) if not success: raise Exception(f'Could not start node {node.instance_name}')
def __get_transaction(self) -> typing.Tuple[JsonDict, JsonDict]: """Fetches transaction and its block from the node if not yet retrieved. Returns: (block, transaction) tuple where first element is Rosetta Block object and second Rosetta Transaction object. """ if self.__block and self.__transaction: return self.__block, self.__transaction timeout = time.monotonic() + 10 while time.monotonic() < timeout: while True: try: block = self._rpc.get_block(block_id=self._block_id) except RuntimeError: block = None if not block: break for tx in block['transactions']: if tx['transaction_identifier']['hash'] == self.hash: related = ', '.join( related['transaction_identifier']['hash'] for related in tx.get('related_transactions', ())) or 'none' logger.info(f'Receipts of {self.hash}: {related}') self.__memoised = (block, tx) return self.__memoised self._block_id = int(block['block_identifier']['index']) + 1 time.sleep(0.25) assert False, f'Transaction {self.hash} did not complete in 10 seconds'
def transfer_between_nodes(nodes): logger.info('Testing transfer between mocknet validators') node = nodes[0] alice = get_validator_account(nodes[1]) bob = get_validator_account(nodes[0]) transfer_amount = 100 get_balance = lambda account: int( node.get_account(account.account_id)['result']['amount']) alice_initial_balance = get_balance(alice) alice_nonce = node.get_nonce_for_pk(alice.account_id, alice.pk) bob_initial_balance = get_balance(bob) logger.info(f'Alice initial balance: {alice_initial_balance}') logger.info(f'Bob initial balance: {bob_initial_balance}') last_block_hash = node.get_latest_block().hash_bytes tx, tx_hash = sign_payment_tx_and_get_hash(alice, bob.account_id, transfer_amount, alice_nonce + 1, last_block_hash) send_transaction(node, tx, tx_hash, alice.account_id) alice_final_balance = get_balance(alice) bob_final_balance = get_balance(bob) logger.info(f'Alice final balance: {alice_final_balance}') logger.info(f'Bob final balance: {bob_final_balance}') # Check mod 1000 to ignore the cost of the transaction itself assert (alice_initial_balance - alice_final_balance) % 1000 == transfer_amount assert bob_final_balance - bob_initial_balance == transfer_amount
def stop_node(node): m = node.machine logger.info(f'Stopping node {m.name}') pid = get_near_pid(m) if pid != '': m.run('bash', input=kill_proccess_script(pid)) m.run('sudo -u ubuntu -i', input=TMUX_STOP_SCRIPT)
def compute_seats(stakes, num_block_producer_seats): max_stake = 0 for i in stakes: max_stake = max(max_stake, i[0]) # Compute seats assignment. l = 0 r = max_stake + 1 seat_price = -1 while r - l > 1: tmp_seat_price = (l + r) // 2 num_seats = 0 for i in range(len(stakes)): num_seats += stakes[i][0] // tmp_seat_price if num_seats <= num_block_producer_seats: r = tmp_seat_price else: l = tmp_seat_price seat_price = r logger.info(f'compute_seats seat_price: {seat_price}') seats = [] for stake, item in stakes: seats.append((stake // seat_price, stake, item)) seats.sort(reverse=True) return seats
def get_up_to(from_, to): global hash_to_height, hash_to_epoch, hash_to_next_epoch, height_to_hash, epochs, first_epoch_switch_height, last_epoch while True: assert time.time() - started < TIMEOUT status = nodes[0].get_status() height = status['sync_info']['latest_block_height'] hash_ = status['sync_info']['latest_block_hash'] block = nodes[0].get_block(hash_) hash_to_height[hash_] = height height_to_hash[height] = hash_ cur_epoch = block['result']['header']['epoch_id'] hash_to_epoch[hash_] = cur_epoch hash_to_next_epoch[hash_] = block['result']['header']['next_epoch_id'] if first_epoch_switch_height is None and last_epoch is not None and last_epoch != cur_epoch: first_epoch_switch_height = height last_epoch = cur_epoch if height >= to: break for i in range(from_, to + 1): hash_ = height_to_hash[i] logger.info( f"{i} {hash_} {hash_to_epoch[hash_]} {hash_to_next_epoch[hash_]}") if len(epochs) == 0 or epochs[-1] != hash_to_epoch[hash_]: epochs.append(hash_to_epoch[hash_])
def start(self, *, boot_node: BootNode = None, skip_starting_proxy=False): if self._proxy_local_stopped is not None: while self._proxy_local_stopped.value != 2: logger.info(f'Waiting for previous proxy instance to close') time.sleep(1) env = os.environ.copy() env["RUST_BACKTRACE"] = "1" env["RUST_LOG"] = "actix_web=warn,mio=warn,tokio_util=warn,actix_server=warn,actix_http=warn," + env.get( "RUST_LOG", "debug") cmd = self._get_command_line(self.near_root, self.node_dir, boot_node, self.binary_name) node_dir = pathlib.Path(self.node_dir) self.stdout_name = node_dir / 'stdout' self.stderr_name = node_dir / 'stderr' with open(self.stdout_name, 'ab') as stdout, \ open(self.stderr_name, 'ab') as stderr: self._process = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=stdout, stderr=stderr, env=env) self._pid = self._process.pid if not skip_starting_proxy: self.start_proxy_if_needed() try: self.wait_for_rpc(10) except: logger.error( '=== failed to start node, rpc does not ready in 10 seconds')
def wrapper(stopped, error, *args): try: func(stopped, error, *args) except Exception as e: traceback.print_exc() logger.info(f"Process {func.__name__} failed with {repr(e)}") error.value = 1
async def main(): key_pair_0 = nacl.signing.SigningKey(key_seed()) tracker = LogTracker(nodes[0]) conn = await connect(nodes[0].addr()) await run_handshake(conn, nodes[0].node_key.pk, key_pair_0, listen_port=12345) num_nodes = 300 def create_update(): key_pairs = [key_pair_0] + [nacl.signing.SigningKey(key_seed()) for _ in range(num_nodes - 1)] nonces = [[1] * num_nodes for _ in range(num_nodes)] edges = [] for i in range(num_nodes): for j in range(i): edge = create_edge(key_pairs[i], key_pairs[j], nonces[i][j]) edges.append(edge) return create_sync_data(edges=edges) asyncio.get_event_loop().create_task(consume(conn)) for i in range(3): update = create_update() logger.info("Sending update...") await conn.send(update) logger.info("Sent...") await asyncio.sleep(1) assert tracker.check("delay_detector: LONG DELAY!") is False
async def handle(self, msg, fr, to): if msg.enum == 'Block': h = msg.Block.BlockV2.header.inner_lite().height logger.info(f"Height: {h}") if h >= 10: logger.info('SUCCESS') success.value = 1 return True
def poll_blocks(node: cluster.LocalNode, *, timeout: float = 120, poll_interval: float = 0.25, __target: typing.Optional[int] = None, **kw) -> typing.Iterable[cluster.BlockId]: """Polls a node about the latest block and yields it when it changes. The function continues yielding blocks indefinitely (so long as the node continues reporting its status) until timeout is reached or the caller stops reading yielded values. Reaching the timeout is considered to be a failure condition and thus it results in an `AssertionError`. The expected usage is that caller reads blocks until some condition is met at which point it stops iterating over the generator. Args: node: Node to query about its latest block. timeout: Total timeout from the first status request sent to the node. poll_interval: How long to wait in seconds between each status request sent to the node. kw: Keyword arguments passed to `BaseDone.get_latest_block` method. Yields: A `cluster.BlockId` object for each each time node’s latest block changes including the first block when function starts. Note that there is no guarantee that there will be no skipped blocks. Raises: AssertionError: If more than `timeout` seconds passes from the start of the iteration. """ end = time.monotonic() + timeout start_height = -1 count = 0 previous = -1 while time.monotonic() < end: latest = node.get_latest_block(**kw) if latest.height != previous: if __target: msg = f'{latest} (waiting for #{__target})' else: msg = str(latest) logger.info(msg) yield latest previous = latest.height if start_height == -1: start_height = latest.height count += 1 time.sleep(poll_interval) msg = 'Timed out polling blocks from a node\n' if count > 0: msg += (f'First block: {start_height}; last block: {previous}\n' f'Total blocks returned: {count}') else: msg += 'No blocks were returned' if __target: msg += f'\nWaiting for block: {__target}' raise AssertionError(msg)
def main(): nodes = [] # Build the container run(('make', 'DOCKER_TAG=' + _DOCKER_IMAGE_TAG, 'docker-nearcore')) try: dot_near = pathlib.Path.home() / '.near' # Initialise local network cmd = f'neard --home /home/near localnet --v {NUM_NODES} --prefix test' docker_run(cmd, volume=(dot_near, '/home/near')) # Start all the nodes for ordinal in range(NUM_NODES): logger.info(f'Starting node {ordinal}') node = DockerNode(ordinal, dot_near / f'test{ordinal}') node.start(boot_node=nodes) nodes.append(node) # Wait for them to initialise for ordinal, node in enumerate(nodes): logger.info(f'Waiting for node {ordinal} to respond') node.wait_for_rpc(10) # Wait for BLOCKS blocks to be generated latest = utils.wait_for_blocks(nodes[0], target=BLOCKS) # Fetch latest block from all the nodes blocks = [] for ordinal, node in enumerate(nodes): utils.wait_for_blocks(node, target=latest.height) response = node.get_block(latest.hash) assert 'result' in response, (ordinal, block) block = response['result'] blocks.append(block) bid = cluster.BlockId.from_header(block['header']) logger.info(f'Node {ordinal} sees block: {bid}') # All blocks should be equal for ordinal in range(1, NUM_NODES): assert blocks[0] == blocks[ordinal], (ordinal, blocks) logger.info('All good') finally: # `docker stop` takes a few seconds so stop all containers in parallel. # atexit we’ll call DockerNode.cleanup method for each node as well and # it’ll handle all the other cleanups. cids = tuple(filter(None, (node._container_id for node in nodes))) if cids: logger.info('Stopping containers') run(('docker', 'stop') + cids) for node in nodes: node._container_id = None subprocess.check_call( ('docker', 'image', 'rm', '-f', _DOCKER_IMAGE_TAG))
def process(): for i in range(100): key = bytearray(8) key[0] = i % 10 res = nodes[4].call_function( acc_id, 'read_value', base64.b64encode(bytes(key)).decode("ascii")) res = int.from_bytes(res["result"]["result"], byteorder='little') assert res == (i % 10) logger.info("all done")
def random_transaction(account, i, node_account, max_tps_per_node): time.sleep(random.random() * mocknet.NUM_ACCOUNTS / max_tps_per_node / 3) choice = random.randint(0, 2) if choice == 0: logger.info(f'Account {i} transfers') send_transfer(account, node_account) elif choice == 1: function_call_set_delete_state(account, i, node_account) elif choice == 2: function_call_ft_transfer_call(account, i, node_account)
async def send_binary(self, raw_message, to, fr=None): writer = self.get_writer(to, fr) if writer is None: logger.info( f"Writer not known: to={to}, fr={fr}, send={self.send_to_map.keys()}, recv={self.recv_from_map.keys()}" ) else: writer.write(struct.pack('I', len(raw_message))) writer.write(raw_message) await writer.drain()
def one_process(ord_, seconds): started = time.time() sent = 0 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(nodes[0].addr()) while time.time() - started < seconds: s.send(struct.pack('I', PACKAGE_LEN)) s.send(buf) sent += PACKAGE_LEN logger.info("PROCESS %s SENT %s BYTES" % (ord_, sent))
async def _handle(self, raw_message, *, writer, sender_port_holder, receiver_port_holder, ordinal_to_writer): sender_ordinal = port_holder_to_node_ord(sender_port_holder) receiver_ordinal = port_holder_to_node_ord(receiver_port_holder) try: message = BinarySerializer(schema).deserialize( raw_message, PeerMessage) assert BinarySerializer(schema).serialize(message) == raw_message if message.enum == 'Handshake': message.Handshake.listen_port += 100 if sender_port_holder[0] is None: sender_port_holder[0] = message.Handshake.listen_port other_ordinal = self.other(sender_ordinal, receiver_ordinal) if other_ordinal is not None and not other_ordinal in ordinal_to_writer: ordinal_to_writer[other_ordinal] = writer decision = await self.handle(message, sender_ordinal, receiver_ordinal) if decision is True and message.enum == 'Handshake': decision = message if not isinstance(decision, bool): decision = BinarySerializer(schema).serialize(decision) return decision except: # TODO: Remove this if raw_message[0] == 13: # raw_message[0] == 13 is RoutedMessage. Skip leading fields to get to the RoutedMessageBody ser = BinarySerializer(schema) ser.array = bytearray(raw_message) ser.offset = 1 ser.deserialize_field(PeerIdOrHash) ser.deserialize_field(PublicKey) ser.deserialize_field(Signature) ser.deserialize_field('u8') # The next byte is the variant ordinal of the `RoutedMessageBody`. # Skip if it's the ordinal of a variant for which the schema is not ported yet if raw_message[ser.offset] in [3, 4, 5, 7]: # Allow the handler determine if the message should be passed even when it couldn't be deserialized return await self.handle(None, sender_ordinal, receiver_ordinal) is not False logger.info(f"ERROR 13 {int(raw_message[ser.offset])}") else: logger.info(f"ERROR {int(raw_message[0])}") raise return True
def init_ft(node_account): tx_res = node_account.send_deploy_contract_tx( '/home/ubuntu/fungible_token.wasm') logger.info(f'ft deployment {tx_res}') mocknet_helpers.wait_at_least_one_block() s = f'{{"owner_id": "{node_account.key.account_id}", "total_supply": "{10**33}"}}' tx_res = node_account.send_call_contract_raw_tx(node_account.key.account_id, 'new_default_meta', s.encode('utf-8'), 0) logger.info(f'ft new_default_meta {tx_res}')