コンテンツにスキップ

ブロック報酬を照会する⚓︎

初級

NEM の各 ブロック は、1 つの ハーベスターアカウント によって生成されます。 ブロックのハーベスティングに対する報酬はすべて、そのブロックに含まれる トランザクション の手数料から生じ、これらの手数料はブロックを生成したハーベスターに全額支払われます。

このチュートリアルでは、任意のブロックを照会し、そのハーベスターを特定して、報酬を構成するトランザクション手数料を合計する方法を説明します。

前提条件⚓︎

始める前に、開発環境をセットアップ してください。

このチュートリアルではネットワークからデータを読み取るだけです。アカウント や XEM の残高は必要ありません。

完全なコード⚓︎

このチュートリアルの完全なコード一覧を以下に示します。 詳細な手順ごとの説明は次のセクションで行います。

import json
import os
import urllib.request

from symbolchain.CryptoTypes import PublicKey
from symbolchain.facade.NemFacade import NemFacade

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
print(f'Using node {NODE_URL}')

BLOCK_HEIGHT = os.getenv('BLOCK_HEIGHT', '661258')

facade = NemFacade('testnet')

try:
    # Fetch the block at the given height
    block_url = f'{NODE_URL}/block/at/public'
    request = urllib.request.Request(
        block_url,
        data=json.dumps({'height': int(BLOCK_HEIGHT)}).encode(),
        headers={'Content-Type': 'application/json'})
    with urllib.request.urlopen(request) as response:
        block = json.loads(response.read())
    transactions = block['transactions']
    print(f'Block height: {BLOCK_HEIGHT}')
    print(f'Transactions: {len(transactions)}')

    # Identify the harvester
    harvester = facade.network.public_key_to_address(
        PublicKey(block['signer']))
    print(f'Harvester: {harvester}')

    # Sum the transaction fees
    total_reward = 0
    print('\nTransaction fees:')
    for transaction in transactions:
        fee = int(transaction['fee'])
        total_reward += fee
        print(f'  Fee: {fee / 1e6:,.6f} XEM')

    # Total reward
    print(f'\nTotal block reward: {total_reward / 1e6:,.6f} XEM')

except Exception as error:
    print(error)

Download source

import { PublicKey } from 'symbol-sdk';
import { NemFacade } from 'symbol-sdk/nem';

const fmt = v => (Number(v) / 1e6).toLocaleString(
    'en-US', { minimumFractionDigits: 6 });

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
console.log(`Using node ${NODE_URL}`);

const BLOCK_HEIGHT = process.env.BLOCK_HEIGHT || '661258';

const facade = new NemFacade('testnet');

try {
    // Fetch the block at the given height
    const blockUrl = `${NODE_URL}/block/at/public`;
    const response = await fetch(blockUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ height: parseInt(BLOCK_HEIGHT, 10) })
    });
    if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);
    const block = await response.json();
    const transactions = block.transactions;
    console.log(`Block height: ${BLOCK_HEIGHT}`);
    console.log(`Transactions: ${transactions.length}`);

    // Identify the harvester
    const harvester = facade.network.publicKeyToAddress(
        new PublicKey(block.signer));
    console.log(`Harvester: ${harvester}`);

    // Sum the transaction fees
    let totalReward = 0n;
    console.log('\nTransaction fees:');
    for (const transaction of transactions) {
        const fee = BigInt(transaction.fee);
        totalReward += fee;
        console.log(`  Fee: ${fmt(fee)} XEM`);
    }

    // Total reward
    console.log(`\nTotal block reward: ${fmt(totalReward)} XEM`);

} catch (error) {
    console.log(error);
}

Download source

//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.3

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Locale;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.NemFacade;
import org.symbol.sdk.nem.Address;

public final class QueryBlockRewards {
    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final String NODE_URL = System.getenv().getOrDefault(
        "NODE_URL", "http://libertalia.nemtest.net:7890");

    private static final String BLOCK_HEIGHT =
        System.getenv().getOrDefault("BLOCK_HEIGHT", "661258");

    // Format an atomic amount as whole XEM with integer math,
    // since amounts can exceed float precision.
    private static String fmt(final BigInteger value) {
        final BigInteger divisor = BigInteger.valueOf(1_000_000);
        final BigInteger[] parts = value.divideAndRemainder(divisor);
        return String.format(Locale.US, "%,d.%06d", parts[0], parts[1]);
    }

    public static void main(final String[] args) {
        try {
            new QueryBlockRewards().run();
        } catch (final Exception ex) {
            System.out.println(null == ex.getMessage()
                ? ex.toString()
                : ex.getMessage());
        }
    }

    private void run() throws IOException, InterruptedException {
        System.out.printf("Using node %s%n", NODE_URL);
        final NemFacade facade = new NemFacade("testnet");

        // Fetch the block at the given height
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        requestBody.put("height", Long.parseLong(BLOCK_HEIGHT));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/block/at/public"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        if (200 != response.statusCode())
            throw new IOException("HTTP error! status: "
                + response.statusCode());
        final JsonNode block = JSON_MAPPER.readTree(response.body());
        final JsonNode transactions = block.get("transactions");
        System.out.printf("Block height: %s%n", BLOCK_HEIGHT);
        System.out.printf("Transactions: %d%n", transactions.size());

        // Identify the harvester
        final Address harvester = facade.network.publicKeyToAddress(
            new CryptoTypes.PublicKey(block.get("signer").asText()));
        System.out.printf("Harvester: %s%n", harvester);

        // Sum the transaction fees
        BigInteger totalReward = BigInteger.ZERO;
        System.out.println("\nTransaction fees:");
        for (final JsonNode transaction : transactions) {
            final BigInteger fee = new BigInteger(
                transaction.get("fee").asText());
            totalReward = totalReward.add(fee);
            System.out.printf("  Fee: %s XEM%n", fmt(fee));
        }

        // Total reward
        System.out.printf("\nTotal block reward: %s XEM%n",
            fmt(totalReward));

    }
}

Download source

スニペットでは、NODE_URL 環境変数を使って NEM API ノードを指定します。 値が指定されていない場合は、デフォルトの テストネット ノードを使用します。

BLOCK_HEIGHT 環境変数で照会するブロックを選択します。 設定されていない場合は、テストネットでハーベスティングされたブロック 661258 をデフォルト値として使用します。

コードの説明⚓︎

コードはブロック高を指定してブロックを取得し、ブロックの署名者公開鍵からハーベスターのアドレスを導出します。 次に、ブロック内のすべてのトランザクションの手数料を合計して、合計報酬を求めます。

ブロック情報を取得する⚓︎

    # Fetch the block at the given height
    block_url = f'{NODE_URL}/block/at/public'
    request = urllib.request.Request(
        block_url,
        data=json.dumps({'height': int(BLOCK_HEIGHT)}).encode(),
        headers={'Content-Type': 'application/json'})
    with urllib.request.urlopen(request) as response:
        block = json.loads(response.read())
    transactions = block['transactions']
    print(f'Block height: {BLOCK_HEIGHT}')
    print(f'Transactions: {len(transactions)}')
    // Fetch the block at the given height
    const blockUrl = `${NODE_URL}/block/at/public`;
    const response = await fetch(blockUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ height: parseInt(BLOCK_HEIGHT, 10) })
    });
    if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);
    const block = await response.json();
    const transactions = block.transactions;
    console.log(`Block height: ${BLOCK_HEIGHT}`);
    console.log(`Transactions: ${transactions.length}`);
        // Fetch the block at the given height
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        requestBody.put("height", Long.parseLong(BLOCK_HEIGHT));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/block/at/public"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        if (200 != response.statusCode())
            throw new IOException("HTTP error! status: "
                + response.statusCode());
        final JsonNode block = JSON_MAPPER.readTree(response.body());
        final JsonNode transactions = block.get("transactions");
        System.out.printf("Block height: %s%n", BLOCK_HEIGHT);
        System.out.printf("Transactions: %d%n", transactions.size());

/block/at/public POST エンドポイントは、指定した高さのブロックに関する情報を返します。 これには、ブロックに含まれるトランザクションの一覧も含まれます。

ハーベスターを特定する⚓︎

    # Identify the harvester
    harvester = facade.network.public_key_to_address(
        PublicKey(block['signer']))
    print(f'Harvester: {harvester}')
    // Identify the harvester
    const harvester = facade.network.publicKeyToAddress(
        new PublicKey(block.signer));
    console.log(`Harvester: ${harvester}`);
        // Identify the harvester
        final Address harvester = facade.network.publicKeyToAddress(
            new CryptoTypes.PublicKey(block.get("signer").asText()));
        System.out.printf("Harvester: %s%n", harvester);

signer フィールドには、ブロックをハーベスティングしたアカウントの 公開鍵 が格納されています。 メソッドは、この公開鍵を対応するテストネットの アドレス に変換します。

署名者が常に報酬を得るアカウントとは限りません

ローカルハーベスティング では、signer が ハーベスターアカウント であり、報酬を受け取ります。

リモートハーベスティング または 委任ハーベスティング では、signer は リモートアカウント ですが、報酬は メインアカウント に支払われます。

トランザクション手数料を合計する⚓︎

    # Sum the transaction fees
    total_reward = 0
    print('\nTransaction fees:')
    for transaction in transactions:
        fee = int(transaction['fee'])
        total_reward += fee
        print(f'  Fee: {fee / 1e6:,.6f} XEM')
    // Sum the transaction fees
    let totalReward = 0n;
    console.log('\nTransaction fees:');
    for (const transaction of transactions) {
        const fee = BigInt(transaction.fee);
        totalReward += fee;
        console.log(`  Fee: ${fmt(fee)} XEM`);
    }
        // Sum the transaction fees
        BigInteger totalReward = BigInteger.ZERO;
        System.out.println("\nTransaction fees:");
        for (final JsonNode transaction : transactions) {
            final BigInteger fee = new BigInteger(
                transaction.get("fee").asText());
            totalReward = totalReward.add(fee);
            System.out.printf("  Fee: %s XEM%n", fmt(fee));
        }

ブロック内の各トランザクションには、原子単位で表された fee フィールドがあります。 XEM の 可分性 は 6 なので、350000 原子単位は 0.350000 XEM を表します。

すべてのトランザクションの手数料を加算すると、ブロックの合計報酬になります。

合計報酬を計算する⚓︎

    # Total reward
    print(f'\nTotal block reward: {total_reward / 1e6:,.6f} XEM')
    // Total reward
    console.log(`\nTotal block reward: ${fmt(totalReward)} XEM`);
        // Total reward
        System.out.printf("\nTotal block reward: %s XEM%n",
            fmt(totalReward));

ブロックの合計報酬は、すべてのトランザクション手数料の合計であり、ハーベスターに全額支払われます。 空のブロックには手数料がないため、報酬もありません。

別の方法: アカウントごとに報酬を照会する

このチュートリアルでは、特定のブロックに含まれるトランザクション手数料を合計して、そのブロックの報酬を計算します。

特定のアカウントが得た報酬に関心がある場合は、/account/harvests GET エンドポイントを使用してください。 ハーベスティングされたブロックごとに 1 件のエントリを返し、そのブロックで得た報酬を示す totalFee フィールドも含みます。

このエンドポイントは、報酬を得るアカウントである ハーベスターアカウント のアドレスを受け取ります。 リモートハーベスティング または 委任ハーベスティング の場合、署名したリモートアカウントではなくメインアカウントです。

リモートアカウントのアドレスを指定しても、メインアカウントに代わって署名したブロックが返されます。

出力⚓︎

以下の出力は、ブロック 661,258 の報酬を照会した場合の実行例です。

Using node http://libertalia.nemtest.net:7890
Block height: 661258
Transactions: 2
Harvester: TCJLCZSOQ6RGWHTPSV2DW467WZSHK4NBSITND4OF

Transaction fees:
  Fee: 0.350000 XEM
  Fee: 0.050000 XEM

Total block reward: 0.400000 XEM

出力の要点は次のとおりです。

  • ハーベスター(4 行目): ブロックの signer 公開鍵から導出されたアドレス。
  • トランザクション手数料(7~8 行目): ブロックに含まれる各トランザクションが支払った手数料。
  • ブロックの合計報酬(10 行目): すべてのトランザクション手数料の合計で、ハーベスターに全額支払われる金額。

まとめ⚓︎

このチュートリアルでは、次の方法を説明しました。

手順 関連ドキュメント
ブロック情報を取得する /block/at/public POST