見出し画像

【Jaguar's Blog 5】SDK Python - NEM Transactions

この記事は、2023年12月23日に投稿されたNEM/Symbolのコア開発者Jaguar氏による記事「SDK Python - NEM Transactions」をChatGPTを用いて翻訳したものです。


Symbol Python SDKはNEMトランザクションの送信をサポートしています。このガイドでは、SDKとaiohttpを使用してトランザクションを非同期に送信する方法について詳しく見ていきます。トランザクション記述子が与えられた場合に、ネットワークにトランザクションを簡単に送信できるような関数を作成します。


Setup

このガイドを使用して新しいプロジェクトを作成することをお勧めします。セットアップが完了したら、依存関係を追加しましょう。

pip install aiohttp
pip install symbol-sdk-python

今、新しいPythonファイルを作成して、以下をトップに追加してください。

import asyncio

from aiohttp import ClientSession
from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.nem.Network import NetworkTimestamp


NEM_PRIVATE_KEY = '<your private key>'
NEM_PRIVATE_KEY_2 = '<your other private key>'
NEM_API_ENDPOINT = 'http://your-testnet-node:7890'

トップレベルの定数を適切な値に設定することを確認してください。

  • NEM_PRIVATE_KEYは、XEMを送信するアカウントのプライベートキーである必要があります。

  • NEM_PRIVATE_KEY_2は、XEMを受信するアカウントのプライベートキーである必要があります。

  • NEM_API_ENDPOINTは、NEMネットワークのノードのエンドポイント(プロトコルとポートを含む)である必要があります。

ℹ️ このガイドでは、実際には2つ目のプライベートキーは必要ありません。代わりに、アドレスを使用することもできます。

NEM_PRIVATE_KEYに資金を供給する必要がある場合は、NEMの蛇口(faucet)を利用できます。

Network Time

NEMブロックチェーンにトランザクションを送信する際、そのトランザクションには現在のネットワーク時間よりも遅くないタイムスタンプが必要です。さらに、デッドラインは現在のネットワーク時間から1日以内である必要があります。最後に、デッドラインはタイムスタンプよりも前になってはいけません。

ℹ️ 有効なタイムスタンプは広範な値を取り得るため、トランザクションの確認時間を判断するのに役立ちません。トランザクションが含まれるブロックの時間がその真の確認時間であり、タイムスタンプの値ではありません。これにより、Symbolトランザクションからはタイムスタンプが削除されました。

これらのプロパティを正しく設定するためには、現在のネットワーク時間をネットワークからクエリする必要があります。それを行うためのget_network_timeという関数を作成しましょう。

async def get_network_time():
    async with ClientSession(raise_for_status=True) as session:
        # initiate a HTTP GET request to a NEM REST endpoint
        async with session.get(f'{NEM_API_ENDPOINT}/time-sync/network-time') as response:
            # wait for the (JSON) response
            response_json = await response.json()

            # extract the network time from the JSON
            timestamp = NetworkTimestamp(int(response_json['receiveTimeStamp'] / 1000))
            return timestamp

まず、ClientSessionを作成し、raise_for_statusを設定して、サーバーエラーを例外に変換できるようにします。次に、route time-sync/network-time に対して GET リクエストを送信します。このルートは、NEMノードのタイム同期ルーチンで使用されていますが、ここでは現在の時間を決定するために使用します。これは、sendTimeStampreceiveTimeStampの2つのプロパティを持つJSONオブジェクトを返します。これらはどちらもミリ秒単位のタイムスタンプです。NEMネットワークは秒単位のタイムスタンプを使用しているため、そのうちの1つ(receiveTimeStamp)を選び、1000で割って秒単位に変換します。最後に、これをNEMのタイムスタンプを表すNetworkTimestampオブジェクトでラップします。これは、追加の時間を追加するためのヘルパー関数を提供します。

Push Transaction to Network

また、トランザクションをネットワークに送信する機能も必要です。NEMでは、トランザクションをネットワークに送信するためには、2つのものを送信する必要があります。

  1. data: シリアル化されたトランザクションデータ

  2. signatur: データの署名

これらの2つのプロパティから構成されるJSON文字列があると仮定しましょう。それをネットワークに送信するためのpush_transactionと呼ばれる関数を作成しましょう。

async def push_transaction(json_payload):
    async with ClientSession(raise_for_status=True) as session:
        # initiate a HTTP POST request to a NEM REST endpoint
        async with session.post(f'{NEM_API_ENDPOINT}/transaction/announce', data=json_payload, headers={
            'Content-Type': 'application/json'
        }) as response:
            # wait for the (JSON) response
            return await response.json()

まず、ClientSessionを作成し、raise_for_statusを設定して、サーバーエラーを例外に変換できるようにします。次に、route transaction/announce に対して POST リクエストを送信します。POSTデータはJSON文字列であり、コンテントタイプをapplication/jsonに設定するようにします。最後に、レスポンスを待ちます。すべてがうまくいった場合、レスポンスにはSUCCESSと等しいmessageプロパティが含まれているはずです。現時点では、このレスポンスオブジェクトをそのまま返します。

ℹ️ NEMでは、JSONの代わりにバイナリオブジェクトフォーマットも利用できます。これはノードが情報をやり取りするためのフォーマットであり、コンテントタイプとしてapplication/binaryを使用して示されます。これに関する詳細な議論は、この記事の範囲外です。

XEM

NEMでは、XEMの単位は最大で小数点以下6桁まであります。完全なXEMユニットの金額として数値を簡単に表記するために、数値を10^6倍する小さな関数を追加しましょう。小さな変更ですが、後で可読性に役立ちます。

def xem(amount):
    return amount * 1000000

Transaction Descriptor

Symbol SDKでは、トランザクションプロパティの辞書はトランザクションデスクリプタと呼ばれます。SDKはこのデスクリプタを受け入れ、トランザクションオブジェクトを返します。

3つの引数を受け入れる関数を書いてみましょう。

facade: (NEM)ネットワークと対話するためのSymbol SDKファサード
signer_key_pair: トランザクションの送信者のキーペアで、トランザクションに署名するために使用されます
transaction_descriptor: 望ましいトランザクションプロパティから構成されるトランザクションデスクリプタ

async def prepare_and_send_transaction(facade, signer_key_pair, transaction_descriptor):

まず、先に書いたget_network_time関数を使用して、ネットワークの時間をクエリする必要があります。

    async with ClientSession(raise_for_status=True) as session:
        # get the current network time from the network
        network_time = await get_network_time(session)

ℹ️ ご注意ください、get_network_time関数を作成するのではなく、ClientSessionを受け入れるように変更しました。可能であれば、各セッションが接続プールをカプセル化しているため、ClientSessionオブジェクトを再利用することをお勧めします。この接続プールを利用すると、アプリケーションのパフォーマンスが向上する可能性があります。

次に、transaction_descriptor引数からトランザクションオブジェクトを作成する必要があります。これは、facadeのトランザクションファクトリのcreateメソッドを使用して行うことができます。

        # create the transaction
        transaction = facade.transaction_factory.create({
            'signer_public_key': signer_key_pair.public_key,
            'timestamp': network_time.timestamp,
            'deadline': network_time.add_hours(1).timestamp,

            **transaction_descriptor
        })

さらに、すべてのトランザクションに共通するいくつかのプロパティを設定します。

signer_public_key: トランザクション署名者の公開キー;signer_key_pairから派生
timestamp: トランザクションのタイムスタンプ;get_network_timeによって取得された現在のネットワーク時間
deadline: timestampから1時間後;注意してください、これはNetworkTimestampが提供するadd_hoursを使用して計算されています。

3番目に、トランザクションに署名し、適切なトランザクションペイロードを構築する必要があります。

 # sign the transaction and attach its signature
        signature = facade.sign_transaction(signer_key_pair, transaction)
        json_payload = facade.transaction_factory.attach_signature(transaction, signature)

sign_transactionsigner_key_pairでトランザクションに署名し、結果の署名を返します。attach_signatureは、シリアル化されたトランザクションデータと署名の両方で構成されるトランザクションペイロードを構築し、これを直接ネットワークに送信できる形にします。

4番目、オプションのステップとして、hash_transactionを呼び出すことでトランザクションのハッシュを計算します。

        # hash the transaction (this is dependent on the signature)
        transaction_hash = facade.hash_transaction(transaction)
        print(f'transaction hash {transaction_hash}')

最後に、先に作成したpush_transaction関数を使用してトランザクションをネットワークに送信します。

       # send the transaction to the network
        push_result = await push_transaction(session, json_payload)
        if 'SUCCESS' == push_result['message']:
            print('transaction was sent and accepted successfully')
        else:
            print('there was an error with the transaction')

        print(push_result)

ここでは、結果のmessageプロパティを確認し、適切なメッセージを出力します。

Usage

さっき書いた関数を使って、どうやってトランザクションをネットワークに送信するか見てみましょう!

まず、テストネットワークのNemFacadeを作成し、2つのアカウントを読み込んでください。

   facade = NemFacade('testnet')

    signer_key_pair = facade.KeyPair(PrivateKey(NEM_PRIVATE_KEY))
    signer_address = facade.network.public_key_to_address(signer_key_pair.public_key)
    print(f'signer address {signer_address}')

    recipient_key_pair = facade.KeyPair(PrivateKey(NEM_PRIVATE_KEY_2))
    recipient_address = facade.network.public_key_to_address(recipient_key_pair.public_key)
    print(f'recipient address {recipient_address}')

次に、適切なディスクリプタを使用して関数を呼び出します。

    await prepare_and_send_transaction(facade, signer_key_pair, {
        'type': 'transfer_transaction_v2',
        'recipient_address': recipient_address,
        'amount': xem(2),
        'fee': xem(1)
    })

この場合、アカウント(NEM_PRIVATE_KEY)から別のアカウント(NEM_PRIVATE_KEY_2)に2 XEMを送るトランザクションを準備して送信しています。NEMでは、新しいアプリケーションではV2のトランザクションを使用することが推奨されているため、transfer_transaction_v2を指定しています。recipient_addressNEM_PRIVATE_KEY_2から派生しています。amountおよびfeeの値は、xemヘルパー関数を利用して、それぞれ2 XEMと1 XEMに拡張されています。

すべてのステップを実行したら、prepare_and_send_transactionを呼び出すことで簡単にNEMトランザクションをネットワークに送信できます!コードは非常にDRY(Don't Repeat Yourself)です。異なるトランザクションを送信するには、prepare_and_send_transactionに渡すトランザクションディスクリプタを変更するだけです。さらに、共通のフィールドはすべて1か所で正しく設定されており、コード(またはディスクリプタ)を混乱させる必要はありません。

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