6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

nem / symbolAdvent Calendar 2024

Day 9

NEMスーパーノードプログラム 毎月のエンロールメントを楽する。

Last updated at Posted at 2024-12-08

毎月のエンロールメントがめんどくさい

ってことで、chatGPTさんというか、perplexityに相談。

もう、どうやって構築していったのかは、忘れたけれど、Pythonが得意らしいので以下のスクリプトを組んでもらった。
各個人の環境で動くかどうかは分かりません。動かないときは都度、AIさんにエラー見せて対応方法を提示してもらったと思う。

※秘密鍵が必要なので、原則的にセキュリティには気を付けてください。

#!/usr/bin/env python3
import random
from symbolchain.nem.KeyPair import KeyPair
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.CryptoTypes import PrivateKey
from symbolchain.nem.Network import NetworkTimestamp
from binascii import unhexlify
import requests
import json

facade = NemFacade('mainnet')

# Sender data: private key and nodehost (replace with your actual data)
sender_data = [
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem01a.symbol-node.com"
    },
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem02.symbol-node.com"
    },
    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem03.symbol-node.com"
    },
]

# Multiple node URLs
node_urls = [
    'https://nem01a.symbol-node.com:7891',
    'https://nem02.symbol-node.com:7891',
    'https://nem03.symbol-node.com:7891',
    'https://nem04.symbol-node.com:7891',
    'https://nem05.symbol-node.com:7891',
    'https://nem06.symbol-node.com:7891',
    'https://nem01.neluta.win:7891',
    'https://nem02.neluta.win:7891',
    'https://nem03.neluta.win:7891',
    'https://nem04.neluta.win:7891',
    'https://nem05.neluta.win:7891',
]

def get_random_node():
    return random.choice(node_urls)

def get_codeword(public_key):
    url = f"https://nem.io/supernode/api/codeword/{public_key}"
    response = requests.get(url)
    if response.status_code == 200:
        return response.text.strip()
    else:
        raise Exception(f"Failed to get codeword for public key {public_key}")

def send_transaction(sender_private_key, recipient_address, amount, message):
    node_url = get_random_node()
    post_string = f"{node_url}/transaction/announce"
    time_string = f"{node_url}/time-sync/network-time"

    # Obtain network time
    networkTimeRequest = requests.get(time_string)
    networkTime = NetworkTimestamp(int(networkTimeRequest.json()['sendTimeStamp'] / 1000))
    
    # Set the deadline to 1 hour in the future
    deadline = networkTime.add_hours(1).timestamp

    senderKeypair = NemFacade.KeyPair(sender_private_key)
    senderPubkey = senderKeypair.public_key
    senderAddress = facade.network.public_key_to_address(senderPubkey)

    # Create a transfer transaction
    transfer = facade.transaction_factory.create({
        'type': 'transfer_transaction_v1',
        'signer_public_key': senderPubkey,
        'recipient_address': recipient_address,
        'deadline': deadline,
        'message': {
            'message_type': 'plain',
            'message': message
        },
        'amount': amount,
        'fee': 200000,  # 0.2 XEM fee
        'timestamp': networkTime.timestamp
    })

    # Sign the transaction and attach the signature
    signature = facade.sign_transaction(senderKeypair, transfer)
    tx = facade.transaction_factory.attach_signature(transfer, signature)

    # Announce the transaction to the network
    r = requests.post(post_string, json=json.loads(tx))
    return r, node_url

def send_transaction_with_retry(sender_private_key, recipient_address, amount, message, max_retries=3):
    for attempt in range(max_retries):
        try:
            response, used_node = send_transaction(sender_private_key, recipient_address, amount, message)
            if response.status_code == 200:
                return response, used_node
            else:
                print(f"Attempt {attempt + 1} failed. Status code: {response.status_code}")
        except Exception as e:
            print(f"試行回数 {attempt + 1} : {str(e)}")

        if attempt < max_retries - 1:
            print("別のノードへ接続をリトライ...")
    
    raise Exception("Failed to send transaction after maximum retries")

def main():
    # Ask for the recipient address
    recipient_address = input("エンロールメントアドレスを入力してください。: ").strip()

    # Validate the recipient address (you may want to add more robust validation)
    if not recipient_address or len(recipient_address) != 40:
        print("無効な受信者アドレスです。有効なNEMアドレスを入力してください。")
        return

    print(f"エンロールメントアドレスへ送信: {recipient_address}")
    print("-------------------------------------------")

    # Send transactions from multiple senders
    for i, sender in enumerate(sender_data):
        sender_key = sender["private_key"]
        nodehost = sender["nodehost"]

        # Derive public key from private key
        key_pair = KeyPair(sender_key)
        public_key = key_pair.public_key

        # Get the codeword directly from the API
        codeword = get_codeword(public_key)

        # Create the message
        message = f"enroll {nodehost} {codeword}"

        amount = 0  # Sending 0 XEM
        try:
            response, used_node = send_transaction_with_retry(sender_key, recipient_address, amount, message)
            print(f"Transaction {i+1}:")
            print(f"  Status Code: {response.status_code}")
            print(f"  Response: {response.json()}")
            print(f"  送信メッセージ: {message}")
            print(f"  使用ノード: {used_node}")
        except Exception as e:
            print(f"Transaction {i+1} {nodehost} リトライに失敗: {str(e)}")
        print()

if __name__ == "__main__":
    main()

以下の部分の*****部分が秘密鍵です。で、登録するhost名か、IPアドレスを間違わないように記述してください。このセットでいくつでも追加できます。
※ここの秘密鍵はアドレス自身の秘密鍵です。委任秘密鍵ではないです。

    {
        "private_key": PrivateKey(unhexlify('****************************************************************')),
        "nodehost": "nem01a.symbol-node.com"
    },

以下の部分は送信時に接続するノードです。複数にしているのは負荷分散の為。どこのノードでもいいですよ。

# Multiple node URLs
node_urls = [
    'https://nem01a.symbol-node.com:7891',
    'https://nem02.symbol-node.com:7891',
    'https://nem03.symbol-node.com:7891',
    'https://nem04.symbol-node.com:7891',
    'https://nem05.symbol-node.com:7891',
    'https://nem06.symbol-node.com:7891',
    'https://nem01.neluta.win:7891',
    'https://nem02.neluta.win:7891',
    'https://nem03.neluta.win:7891',
    'https://nem04.neluta.win:7891',
    'https://nem05.neluta.win:7891',
]

スクリプトを実行すると、エンロールメント用のアドレスを聞いてきますので、最新のアドレスを入力し、エンターで実行されます。

※あと、手数料を0.2xem固定にしてますが、host名が長いと足らないかもです。

ということで、月一回のイベントですがこれで楽ができます。

くれぐれも言いますが、セキュリティは本当に気を付けてください。秘密鍵が平文で羅列してるだけですので。何か良い方法があれば教えて。
私は、外部からはアクセスできないサーバー内より実行してます。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?