Hi developers,
i needs some detailed support please. I created some native asset on Cardano, using Cardano cli on Windows. Wanted then to withdrawal some of the token to another address. That worked so far. Once the token were sent, my node status was at 99.99% and showed invalid blocks error and couldn’t get over that percentage until i restarted the node.
Then i did a little bit of research and noticed that i might need to update my mainnet-topology.json file which still just points to IOHK relay node, to fix that node status issue:
{
"Producers": [
{
"addr": "relays-new.cardano-mainnet.iohk.io",
"port": 3001,
"valency": 2
}
]
}
So i found on this Github here some mainnet topology updater script, which didnt work initially so i did some modifications with ChatGPT. Code below.
when i run the topologyUpdater.py i assume my mainnet-topology.json file should get modified, but it doesnt.
Am i on the correct path here?
Thank you
PS C:\Users\username\AppData\Roaming\Cardano> python .\topologyUpdater.py
############################################################
# Reminder: #
# - Ensure VPN is disabled when running this script #
# - Make sure topologyUpdater.json is correctly configured #
# - Cardano node must be running #
############################################################
{ "resultcode": "201", "datetime":"2024-02-01 19:12:50", "clientIp": "my external IP address", "iptype": 4, "msg": "nice to meet you" }
topologyUpdater.py@01.02.2024 20:12:52: got bad resultcode = 402 for fetch command
PS C:\Users\username\AppData\Roaming\Cardano>
topologyUpdater.py
#!/usr/bin/python3
#
# topologyUpdater as python variant
import os
import sys
import argparse
import requests
import json
import subprocess
import time
import smtplib
import email.utils
from email.mime.text import MIMEText
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
from datetime import datetime
version = "0.8"
# define some default values upfront
cnode_valency_default = "1"
ipVersion_default = "4" # other possible values are "6" or "mix"
max_peers_default = "15"
nwmagic_default = "764824073" # network magic for mainnet - if you want a testnet then change the value in config file instead
metricsURL_default = "http://localhost:12798/metrics"
def timestamp4log():
return datetime.now().strftime("%d.%m.%Y %H:%M:%S");
def sendmail(email_config,message):
ts_message = os.path.basename(sys.argv[0]) + "@" + timestamp4log() + ": " + message
if not bool(email_config):
print(ts_message)
else:
msg = MIMEText(ts_message)
msg['To'] = email.utils.formataddr(('Admin', email_config['msgTo']))
msg['From'] = email.utils.formataddr(('Monitoring', email_config['msgFrom']))
msg['Subject'] = email_config['msgSubject']
server = smtplib.SMTP(email_config['smtpServer'], email_config['smtpPort'])
#server.set_debuglevel(True) # show communication with the server
try:
server.sendmail(email_config['msgFrom'], [email_config['msgTo']], msg.as_string())
finally:
server.quit()
return;
def exception_handler(exception_type, exception, traceback):
# All your trace are belong to us!
# your format
sendmail(ec, exception_type.__name__ + ", " + str(exception))
def allowed_gai_family() -> socket.AddressFamily:
family = socket.AF_INET
return family;
def getconfig(configfile):
with open(configfile) as f:
data = json.load(f)
return data;
def requestmetric(url):
try:
response = requests.get(url)
response.raise_for_status()
except requests.exceptions.HTTPError as errh:
sendmail(ec, "Http Error: " + repr(errh))
sys.exit()
except requests.exceptions.ConnectionError as errc:
sendmail(ec,+ "Error Connecting: " + repr(errc))
sys.exit()
except requests.exceptions.Timeout as errt:
sendmail(ec, + "Timeout Error: " + repr(errt))
sys.exit()
except requests.exceptions.RequestException as err:
sendmail(ec, + "OOps: Something Else " + repr(err))
sys.exit()
finally:
return response;
def get_current_block_number():
try:
# Set the environment variable for CARDANO_NODE_SOCKET_PATH
os.environ['CARDANO_NODE_SOCKET_PATH'] = '\\\\.\\pipe\\cardano-node'
# Execute the cardano-cli command and capture the output
cardano_cli_command = ['cardano-cli', 'query', 'tip', '--mainnet']
result = subprocess.run(cardano_cli_command, capture_output=True, text=True, check=True)
data = json.loads(result.stdout)
return str(data['block'])
except subprocess.CalledProcessError as e:
sendmail(ec, "Error executing cardano-cli: " + str(e))
return "0" # Return a default value or handle the error appropriately
except Exception as e:
sendmail(ec, "General error: " + str(e))
return "0"
def print_startup_message():
print("\n############################################################")
print("# Reminder: #")
print("# - Ensure VPN is disabled when running this script #")
print("# - Make sure topologyUpdater.json is correctly configured #")
print("# - Cardano node must be running #")
print("############################################################\n")
# main
if __name__ == "__main__":
print_startup_message()
ec = {}
sys.excepthook = exception_handler
# start with parsing arguments
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--fetch", help="only fetch of a fresh topology file", action="store_true")
parser.add_argument("-p", "--push", help="only node alive push to Topology Updater API", action="store_true")
parser.add_argument("-v", "--version", help="displays version and exits", action="store_true")
parser.add_argument("-c", "--config", help="path to config file in json format", default="topologyUpdater.json")
args = parser.parse_args()
myname = os.path.basename(sys.argv[0])
if args.version:
print(myname + " version " + version)
sys.exit()
if not args.push and not args.fetch:
do_both = True
else:
do_both = False
if args.config:
my_config = getconfig(args.config)
if 'email' in my_config:
ec = my_config['email']
if 'hostname' in my_config:
cnode_hostname = my_config['hostname']
else:
sendmail(ec,"relay hostname parameter is missing in configuration")
sys.exit()
if 'port' in my_config:
cnode_port = my_config['port']
else:
sendmail(ec,"relay port parameter is missing in configuration")
sys.exit()
if 'valency' in my_config:
cnode_valency = my_config['valency']
else:
cnode_valency = cnode_valency_default
if 'ipVersion' in my_config:
ipVersion = my_config['ipVersion']
else:
ipVersion = ipVersion_default
if 'maxPeers' in my_config:
max_peers = my_config['maxPeers']
else:
max_peers = max_peers_default
if 'metricsURL' in my_config:
metricsURL = my_config['metricsURL']
else:
metricsURL = metricsURL_default
if 'destinationTopologyFile' in my_config:
destination_topology_file = my_config['destinationTopologyFile']
else:
sendmail(ec,"destination for topology file is missing in configuration")
sys.exit()
if 'logfile' in my_config:
logfile = my_config['logfile']
else:
sendmail(ec,"path for logfile is missing in configuration")
sys.exit()
if 'networkMagic' in my_config:
nwmagic = my_config['networkMagic']
else:
nwmagic = nwmagic_default
# force to use ipv4 for version 4 configuration
if ipVersion == "4":
urllib3_cn.allowed_gai_family = allowed_gai_family
if args.push or do_both:
# first get last block number
response = requestmetric(metricsURL)
response_list = response.text.splitlines(True)
metrics = {}
for element in response_list:
metric = element.rstrip('\n').split()
metrics[metric[0]] = metric[1]
# Get the current block number using cardano-cli
block_number = get_current_block_number()
# now register with central
url = "https://api.clio.one/htopology/v1/?port=" + cnode_port + "&blockNo=" + block_number + "&valency=" + cnode_valency
url = url + "&magic=" + nwmagic + "&hostname=" + cnode_hostname
response = requests.get(url)
print(response.text)
log = open(logfile, "a")
n = log.write(response.text)
log.close()
# check resultcode
ok_responses = [ '201', '203', '204' ]
parsed_response = json.loads(response.text)
if (parsed_response['resultcode'] not in ok_responses): # add 201 and 203 because those are starting codes
sendmail(ec, "got bad resultcode = " + parsed_response['resultcode'] + " for push command")
if args.fetch or do_both:
url = "https://api.clio.one/htopology/v1/fetch/?max=" + max_peers + "&magic=" + nwmagic + "&ipv=" + ipVersion
response = requests.get(url)
parsed_response = json.loads(response.text)
if (parsed_response['resultcode'] != '201'):
sendmail(ec, "got bad resultcode = " + parsed_response['resultcode'] + " for fetch command")
sys.exit()
# Add custom peers
for peer in my_config['customPeers']:
parsed_response['Producers'].append(peer)
# Write topology to file
topology_file = open(destination_topology_file, "wt")
n = topology_file.write(json.dumps(parsed_response, indent=4))
topology_file.close()
topologyUpdater.json config:
{
"maxPeers": "15",
"metricsURL": "http://localhost:12798/metrics",
"destinationTopologyFile": "C:\\Users\\username\\AppData\\Roaming\\Cardano\\configuration\\cardano\\mainnet-topology.json",
"networkMagic": "764824073",
"customPeers": [
{
"addr": "relays-new.cardano-mainnet.iohk.io",
"port": 3001,
"valency": 1
}
],
"hostname": "my external IP address",
"port": "3001",
"logfile": "C:\\Users\\username\\AppData\\Roaming\\Cardano\\configuration\\cardano\\topologyUpdater.log"
}
Below the python script source code to withdrawal the token.
sendFromWallet.py:
import subprocess
import os
def query_utxos(wallet_address):
socket_path = "\\\\.\\pipe\\cardano-node"
os.environ['CARDANO_NODE_SOCKET_PATH'] = socket_path
cmd = f"cardano-cli query utxo --address {wallet_address} --mainnet"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error querying UTXOs. Make sure cardano-cli is installed and the wallet address is correct.")
print("Error message:", result.stderr)
exit(1)
return result.stdout
def parse_utxos(utxo_data):
utxos = {}
for line in utxo_data.splitlines()[2:]: # Skipping headers
parts = line.split()
tx_hash, tx_ix, *rest = parts
utxos[(tx_hash, tx_ix)] = ' '.join(rest)
return utxos
def extract_token_amount(token_string, full_token_identifier):
# Split the token string at spaces and search for the full token identifier
parts = token_string.split()
for i, part in enumerate(parts):
if full_token_identifier in part:
# The token amount should be the part just before the full token identifier
return int(parts[i - 1])
return 0
def build_transaction(tx_hash, tx_ix, source_address, target_address, token_amount, full_token_identifier, lovelace_amount, total_source_lovelace, fee, total_source_token_amount):
change_lovelace = total_source_lovelace - lovelace_amount - fee
change_token_amount = total_source_token_amount - token_amount
tx_out_change = f"{source_address}+{change_lovelace}"
if change_token_amount > 0:
tx_out_change += f'+"{change_token_amount} {full_token_identifier}"'
tx_out_recipient = f"{target_address}+{lovelace_amount}"
if token_amount > 0:
tx_out_recipient += f'+"{token_amount} {full_token_identifier}"'
tx_command = f'cardano-cli transaction build-raw --tx-in {tx_hash}#{tx_ix} --tx-out {tx_out_change} --tx-out {tx_out_recipient} --fee {fee} --out-file tx.raw'
return tx_command
def calculate_fee(tx_draft_file):
cmd = f"cardano-cli transaction calculate-min-fee --tx-body-file {tx_draft_file} --tx-in-count 1 --tx-out-count 2 --witness-count 1 --mainnet --protocol-params-file protocol.json"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error calculating fee. Make sure cardano-cli is installed and the parameters are correct.")
print("Error message:", result.stderr)
exit(1)
fee = result.stdout.split()[0]
return int(fee)
def sign_transaction(signing_key_file):
cmd = f"cardano-cli transaction sign --tx-body-file tx.raw --signing-key-file {signing_key_file} --mainnet --out-file tx.signed"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error signing the transaction. Make sure the signing key file is correct.")
print("Error message:", result.stderr)
exit(1)
def submit_transaction():
cmd = "cardano-cli transaction submit --tx-file tx.signed --mainnet"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error submitting the transaction.")
print("Error message:", result.stderr)
exit(1)
def main():
wallet_address = "sourceAddress"
target_address = "targetAddress"
policy_id = "policyID"
token_name = "tokenName"
full_token_identifier = f"{policy_id}.{token_name}"
token_amount = 10
lovelace_amount = 2000000
utxo_data = query_utxos(wallet_address)
utxos = parse_utxos(utxo_data)
print("\nAvailable UTXOs:")
for (tx_hash, tx_ix), details in utxos.items():
print(f"{tx_hash}#{tx_ix} : {details}")
found_utxo = False
for (tx_hash, tx_ix), details in utxos.items():
details_parts = details.split('+')
total_source_lovelace = int(details_parts[0].split()[0])
total_source_token_amount = 0
if full_token_identifier and len(details_parts) > 1:
total_source_token_amount = extract_token_amount(details_parts[1], full_token_identifier)
found_utxo = True
if found_utxo:
draft_tx_command = build_transaction(tx_hash, tx_ix, wallet_address, target_address, token_amount, full_token_identifier, lovelace_amount, total_source_lovelace, 0, total_source_token_amount)
subprocess.run(draft_tx_command, shell=True)
fee = calculate_fee("tx.raw")
final_tx_command = build_transaction(tx_hash, tx_ix, wallet_address, target_address, token_amount, full_token_identifier, lovelace_amount, total_source_lovelace - fee, fee, total_source_token_amount)
subprocess.run(final_tx_command, shell=True)
print(f"\nFinal Transaction Command: \n{final_tx_command}")
signing_key_file = "wallets/payment.skey"
sign_transaction(signing_key_file)
submit_transaction()
print("Transaction submitted successfully.")
break
if not found_utxo:
print("\nNo suitable UTXO found.")
if __name__ == "__main__":
main()