見出し画像

【Symbol Blog】シンボルノードの合計ハーベスティング残高の取得

この記事は2023年2月12日にSymbol Blogにて投稿された記事「12 FEB OBTAINING TOTAL HARVESTING BALANCES FOR SYMBOL NODES」をChatGPTを用いて翻訳されたものです。



今日は、SymbolのすべてのAPIノードで収穫アカウントの残高を取得する方法について、簡単なブログ投稿を書こうと思いました。Symbolが立ち上がってから間もなく、私はこれを行うための非常に基本的なPerlスクリプトを書きましたが、処理が非常に遅かったので、先週、2021年よりもSymbolについてより多くの知識を得た今、この問題に再度取り組むことにしました。GitHubで利用可能なJupyter Notebookで提示されているPythonコードの基本的な部分を説明していきます。

このコードを実行するには、APIノードにログインし、mongoDBにアクセスできる必要があります。データベースから直接データにアクセスしているため、処理が大幅に高速化されます。

いつも通り、最初にいくつかのパッケージをインポートする必要があります(まだインストールしていない場合は、pipを使用してこれらをインストールする必要があります)。

from pymongo import MongoClient
import base64
from sshtunnel import SSHTunnelForwarder
from binascii import hexlify, unhexlify
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.CryptoTypes import PublicKey
import requests
from tqdm import tqdm
from collections import defaultdict

ノードのユーザー名とパスワードを使用してノードに接続するためにsshtunnelを使用し、データベースのクエリにはpymongoを使用します。まず、ノードのログイン詳細を指定し、Dockerで実行されているmongoDBへの接続を設定します。

MONGO_HOST = "***" # Add your node address here

server = SSHTunnelForwarder(
    (MONGO_HOST,22),
    ssh_username='***', # Add your node's login username
    ssh_password='***', # Add your node's login username
    remote_bind_address=('172.20.0.2', 27017)
  )
server.start()

client = MongoClient('localhost', server.local_bind_port)
print(server.local_bind_port)
print("connected")

次に、クエリするノードのリストを読み込む必要があります。これは、ノードのアドレスのリストであり、例えば symbolblog-testnet.com、xymharvesting.net などが新しい行で区切られているべきです。この場合、私は symbolnodes.org から収集したすべてのノードを含むファイル("list.txt" として保存)を読み込んでいます。

# Read in list of node addresses

nodes = []  # Initialize an empty list to store nodes
try:
    with open("list.txt", 'r') as file:
        for line in file:
            nodes.append(line.strip())
except FileNotFoundError:
    print(f"File not found: {list.txt}")

わかりました、ノードのリストがあります。では、このリストを繰り返し処理して、そのノードでアクティブにハーベスティングしているアカウントを見つけたいですね。ノード固有の情報はデータベースに保存されていないため、mongoDBから直接これを行うことはできません。そのため、RESTクエリを使用して node/unlockedaccount エンドポイントを調べ、各ノードを個別にクエリする必要があります。ノードが到達不能な場合にプロセスがただ中断しないように、10秒のタイムアウトを追加し、ノードをキーとして、その値としてアカウントの公開鍵のリストを格納する辞書を作成しました。

# Function to fetch JSON data for unlocked accounts (harvesters on the node)
def fetch_json_data(node, timeout=10):  # timeout of 10 seconds
    url = f"http://{node}:3000/node/unlockedaccount"
    try:
        response = requests.get(url, timeout=timeout)
        response.raise_for_status()
        data = response.json()
        return data
    except requests.Timeout:
        print(f"Request to {url} timed out.")
        return None
    except requests.RequestException as e:
        print(f"Request to {url} failed: {e}")
        return None

# Create a dictionary to store nodes and harvesters
node_details = {}

# Loop through each node, fetch JSON data, and add it to the dictionary
for node in tqdm(nodes, desc="Fetching JSON Data"):  # tqdm wrapper for progress bar
    json_data = fetch_json_data(node)
    if json_data and 'unlockedAccount' in json_data:
        node_details[node] = json_data['unlockedAccount']

現在、ノード名に収穫アカウントの公開鍵をマッピングしたデータ構造「node_details」があります。次の問題は、公開鍵をアドレスに変換する必要があることです。これはSymbol Python SDKを使用して行うことができます。また、前のステップの結果から関連付けられた「メイン」アカウントを見つける必要もあります。前のステップの結果は、関連アカウントがゼロ残高になるためです。

以下のコードでは、タイプ16716のトランザクションを探しています。これはアカウントリンクのトランザクションで、linkActionが1である場合はアカウントがリンクされていることを意味します。mongoDBから返されるデータはバイナリ形式であり、再びアドレスではなく公開鍵を表します。ここでは、バイナリの公開鍵データをデコードし、SDKを使用してメイン(残高を含む)およびリンクされたアドレスのアドレスを取得します。関連アドレスからメインアドレスへのマッピングを辞書「mapping」に格納します。

最後に、新しい辞書を作成します。この辞書は、各ノードでハーベスティングしているアカウントのメイン残高を格納します。これは、「mapping」で関連アカウントからメインアカウントへのマッピングを検索することで行います。

facade = SymbolFacade('mainnet')

db = client['catapult']
collection = db.transactions
mapping = defaultdict(dict)

out = collection.find({'transaction.type': 16716})
for x in out:
    if x['transaction']['linkAction'] == 1:
        main = str(facade.network.public_key_to_address(PublicKey((hexlify(x['transaction']['signerPublicKey']).decode('utf8')))))    
        link = str(facade.network.public_key_to_address(PublicKey((hexlify(x['transaction']['linkedPublicKey']).decode('utf8')))))
        mapping[link] = main

address_details = defaultdict(list)
for node, public_keys in node_details.items():
    for pk in public_keys:
        address = str(facade.network.public_key_to_address(PublicKey(pk)))
        if address in mapping:  # Check if the address is in mapping
            address_details[node].append(mapping[address])
        else:
            address_details[node].append(address)

これで、各ノードでハーベスティングしているすべてのアカウントのメインアドレスを含む「address_details」という辞書ができました。次の問題は、これらのアカウントの残高を取得する必要があることです。これまでの手順では、「address_details」の各アカウントの残高を取得するためにmongoDBにクエリを発行していましたが、今回はネットワークに知られているすべてのアドレスの残高を取得します。これにより、バイナリのmongoDBアドレスをデコードし、彼らが所有するsymbol.xym(モザイクID 7777031834025731064)の量を取得します。アドレスをXYM残高にマッピングする辞書「balances」を作成します。

db = client['catapult']
collection = db.accounts
facade = SymbolFacade('mainnet')

out = collection.find()

balances = {}

for x in out:
    xym = 0
    address = str(base64.b32encode(x['account']['address']).decode('utf8')[0:39])
    mosaics = x['account']['mosaics']
    for mos in mosaics:
        if (mos['id'] == 7777031834025731064):
            balances[address] = mos['amount']/1000000
            xym = 1
    if xym == 0:
        balances[address] = 0

これまでのところ、順調ですね。私たちは「address_details」を持ち、メインアカウントをノードにマッピングし、「balances」にはネットワーク上のすべてのアカウントの残高が含まれています。これで、各ノードとそのハーベスティングアドレスを繰り返し処理し、その残高を新しい辞書「balance_details」に追加するだけです。

balance_details = defaultdict(list)

for node, addresses in address_details.items():
    for address in addresses:
        try:
            balance_details[node].append(balances[address])
        except:
            balance_details[node].append(0)

辞書「balance_details」のキーはノードのアドレスであり、その値はそのノードでハーベスティングしているすべてのメインアカウントの残高のリストです。次に、各ノードのハーベスターの数、ノードのメインアカウント残高、およびそのノードでハーベスティングされたすべてのXYMの合計など、いくつかの統計情報を出力し、これをtsvファイルに出力したいです。

with open('nodes_balances.tsv', 'w') as file:
    file.write("Node\tHarvesters\tMain\tTotal\n")
    
    # Iterate through the dictionary and write the required information
    for node, balances in balance_details.items():
        list_size = len(balances)
        main = balances[0] if balances else 'N/A'  # Handle empty list case
        total = sum(balances)
        file.write(f"{node}\t{list_size}\t{main}\t{total}\n")

辞書のキーは常にノードのアドレスなので、これをタブ区切りの出力に表示できます。次に、そのキーに割り当てられた値(リスト)のサイズを調べることで、ノード上のハーベスターの数を見つけることができます。メインのハーベスティングアカウントは常にリストの最初のエントリであり、したがってノードアカウントの残高には位置0を使用できます。最後に、リスト内のすべての残高を合計して、ノードの総合ハーベスティング残高を取得できます。

WRAP-UP

したがって、これは十分にシンプルなスクリプトですが、mongoDBとPython SDKを使用することで、元のPerlコードに比べて大幅な高速化が実現しました。約60倍の高速化があり、数分で実行されるので、コードをより効率的に書き直すのに少し時間を費やして良かったです。ノードリスト結果は以下にあり、完全なJupyter Notebookはこちらで確認できます。

著者(NineLives)について

私はシンボルとNEMの熱狂的なファンで、英語圏でこのプラットフォームの認知度を高めようとこのブログを運営しています。シンボルに関するニュースや、掲載を希望される記事がありましたら、ぜひお知らせください!


この記事が気に入ったらサポートをしてみませんか?