• No products in the cart.

Tracing Stuck Cosmos IBC Transactions

Getting Transaction Hashes and Amounts for Stuck Cosmos IBC Transactions

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)
November 16, 2021

0 responses on "Tracing Stuck Cosmos IBC Transactions"

Leave a ReplyCancel reply

top
2020-2021 Copyright iDevOps.io

Discover more from iDevOps.io

Subscribe now to keep reading and get access to the full archive.

Continue reading

Exit mobile version
X