If you are doing IBC on your Cosmos network you have probably run into situations where the client becomes expired and IBC packets get stuck in the queue. Obviously you want to ensure your customers money isn’t lost. In some cases if you aren’t on Cosmos SDK .44 or higher and you haven’t enabled the IBC-GO module in your network then you can’t run the governance request to update the IBC client.
Thus, you will need to find a way to trace the packets so you know which customers to refund and how much. The trouble with this is neither the Hermes, Go, or TS-relayer have this ability. Also, there isn’t really any documentation on how you would accomplish this.
Here is a script that does just that. You feed it a list of transaction packet ids to search for and it will search using the chains binary to find the transaction hashes for each stuck packet using Cosmos event query. Then it will look up each transaction that matches the list of transactions you’re looking for and get the data you need to issue a refund. The script also keeps a list of page numbers that fails as it goes through the pagenation of the chain you are searching.
import json
import subprocess
import os
import base64
from lxml import html
import requests
def run_terminal_command(command):
command_eddition = "export PATH=~/go/bin:$PATH &&"
command = command_eddition + command
result = subprocess.run(command, stdout=subprocess.PIPE, shell=True)
result = result.stdout.decode('utf-8')
return result
def get_transactions(start, end, transactions, page_errors):
print("Checking Transactions...")
for pageNumber in range(start, end):
print("Pagenation Number: ", pageNumber)
try:
packet_sequence = 0
command = os.environ[
"binary_name"] + " query txs --events message.action='/ibc.core.client.v1.MsgUpdateClient' --node " + \
os.environ["rpc_url"] + " --output json --page " + str(pageNumber)
command_output = run_terminal_command(command)
json_transactions = json.loads(command_output)
for transaction in json_transactions["txs"]:
for log in transaction["logs"]:
for event in log["events"]:
if event["type"] == "acknowledge_packet":
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
elif event["type"] == "recv_packet":
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
elif event["type"] == "write_acknowledgement":
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
elif event["type"] == "transfer":
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
elif event["type"] == "timeout_packet":
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
elif "packet_sequence" in str(event):
#Copy
packet_src_channel = ""
packet_src_connection = ""
packet_dst_channel = ""
packet_dst_connection = ""
#Set Attributes
for attribute in event["attributes"]:
if attribute["key"] == "packet_sequence":
packet_sequence = str(attribute["value"])
if attribute["key"] == "packet_src_channel":
packet_src_channel = str(attribute["value"])
if attribute["key"] == "packet_dst_channel":
packet_dst_channel = str(attribute["value"])
print(packet_sequence,
packet_src_channel,
packet_src_connection,
packet_dst_channel,
packet_dst_connection,
os.environ["packet_src_channel_check"],
os.environ["packet_dst_channel_check"],
)
if packet_src_channel == os.environ["packet_src_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_dst_channel_check"]:
print("matched packet_src_channel")
if packet_dst_channel == os.environ["packet_src_channel_check"]:
print("matched packet_dst_channel")
if packet_src_channel == os.environ["packet_src_channel_check"] and \
packet_dst_channel == os.environ["packet_dst_channel_check"] or \
packet_src_channel == os.environ["packet_dst_channel_check"] and \
packet_dst_channel == os.environ["packet_src_channel_check"]:
transactions[str(packet_sequence)] = {
"txhash": transaction["txhash"],
"type": event["type"],
"height": transaction["height"],
"gas_wanted": transaction["gas_wanted"],
"gas_used": transaction["gas_used"],
"packet_src_channel": packet_src_channel,
"packet_dst_channel": packet_dst_channel
}
except Exception as e:
page_errors.append(pageNumber)
print(e)
return transactions, page_errors
def parse_transactions(transactions, look_for_transactions, found_transactions, page_errors):
print(" ")
print(" ")
print("Get Current Token Price")
terra_request = requests.get(os.environ["coinmarket_url"])
tree = html.fromstring(terra_request.content)
results = tree.xpath("/html/body/div/div[1]/div/div[2]/div/div[1]/div[3]/div/div[2]/div[1]/div")
results_text = results[0].text
current_token_price = float(results_text.replace("$", ""))
print("Current Token price USD: ", str(current_token_price))
print(" ")
print(" ")
print("Transaction Check Done.")
print("Parse Transactions and Find Queued Hashes.")
for packet_sequence in transactions:
print("Looking for Packet Sequence", packet_sequence, "In Queued Transaction List")
for index, packet in enumerate(look_for_transactions):
if packet_sequence == packet:
print("Remove packet from looking list.")
look_for_transactions.pop(index)
print("Found Packet Sequence in Transaction List")
txhash = transactions[packet_sequence]["txhash"]
type = transactions[packet_sequence]["type"]
height = transactions[packet_sequence]["height"]
gas_wanted = transactions[packet_sequence]["gas_wanted"]
gas_used = transactions[packet_sequence]["gas_used"]
print("Looking up found transaction")
transaction_check = "terrad query tx " + txhash + " --output json --node " + os.environ["rpc_url"]
command_output = run_terminal_command(transaction_check)
transaction_metadata = json.loads(command_output)
ibc_transaction_data = base64.b64decode(
transaction_metadata["tx"]["body"]["messages"][1]["packet"]["data"])
ibc_transaction_object = json.loads(ibc_transaction_data)
amount = float(ibc_transaction_object["amount"])
denom = ibc_transaction_object["denom"]
receiver = ibc_transaction_object["receiver"]
sender = ibc_transaction_object["sender"]
adjusted_decimal_amount = float(amount) / float(os.environ["decimal_precision"])
if denom == os.environ["denom_name"]:
cost_per_ibc_transactions = adjusted_decimal_amount * current_token_price
else:
cost_per_ibc_transactions = adjusted_decimal_amount
found_transactions.append({"packet_sequence": packet_sequence,
"txhash": txhash,
"type": type,
"height": height,
"gas_wanted": gas_wanted,
"gas_used": gas_used,
"amount": amount,
"denom": denom,
"receiver": receiver,
"sender": sender,
"adjusted_decimal_amount_of_token": adjusted_decimal_amount,
"current_terra_price_usd": current_token_price,
"cost_in_USD": cost_per_ibc_transactions
})
print(" ")
print(" ")
print("Found Transactions")
print("packet_sequence",
",",
"txhas",
",",
"type",
",",
"gas_wanted",
",",
"gas_used",
",",
"amount",
",",
"denom",
",",
"receiver",
",",
"sender",
",",
"adjusted_decimal_amount_of_token_luna",
",",
"current_token_price",
",",
"cost_in_USD")
for tx in found_transactions:
print(tx["packet_sequence"],
",",
tx["txhash"],
",",
tx["type"],
",",
tx["gas_wanted"],
",",
tx["gas_used"],
",",
tx["amount"],
",",
tx["denom"],
",",
tx["receiver"],
",",
tx["sender"],
",",
tx["adjusted_decimal_amount_of_token"],
",",
tx["current_terra_price_usd"],
",",
tx["cost_in_USD"])
print(" ")
print(" ")
print("Not Found Transactions")
for tx in look_for_transactions:
print(tx)
print(" ")
print(" ")
print("Pages with Errors")
for page in page_errors:
error_page = int(page)
two_before = error_page - 2
two_after = error_page + 2
print("Failed Page: ", page)
# transactions, page_errors = get_transactions(two_before, two_after, transactions, page_errors)
# transactions, look_for_transactions, found_transactions, page_errors = parse_transactions(transactions,
# look_for_transactions,
# found_transactions,
# page_errors)
return transactions, look_for_transactions, found_transactions, page_errors
print("Configure Variables for Script")
found_transactions = []
transactions = {}
os.environ["binary_name"] = "terrad"
os.environ["denom_name"] = "uluna"
os.environ["decimal_precision"] = "1000000"
os.environ["coinmarket_url"] = "https://coinmarketcap.com/currencies/terra-luna/"
os.environ["rpc_url"] = "http://public-node.terra.dev:26657"
os.environ["packet_dst_channel_check"] = "channel-18"
os.environ["packet_src_channel_check"] = "channel-7"
"""
sifchain
connection-21
channel-18
terra
connection-19
channel-7
"""
page_errors = []
look_for_transactions = [
"389",
"390",
"391",
"392",
"393",
"394",
"395",
"396",
"397",
"398",
"399",
"400",
"401",
"402",
"403",
"404",
"405",
"406",
"407",
"408",
"409",
"410",
"411",
"412",
"413",
"414",
"415",
"416",
"420",
"423",
"424",
"425",
"426",
"427",
"428",
"429",
"430",
"435",
"471",
"472",
"473",
"474",
"479",
"480",
"551",
"552",
"553",
"554",
"555",
"556",
"557",
"558",
"559",
"560",
"561",
"562",
"563",
"564",
]
transactions, page_errors = get_transactions(168, 600, transactions, page_errors)
transactions, look_for_transactions, found_transactions, page_errors = parse_transactions(transactions, look_for_transactions, found_transactions, page_errors)
0 responses on "Tracing Stuck Cosmos IBC Transactions"