コンテンツにスキップ
初級

ブロック報酬の照会⚓︎

Symbol上の各ブロックは、インフレーションと、そのブロックで収集されたトランザクション手数料からなる報酬を生成します。 この報酬はその後、ハーベスタ、ノードの受益者(ベネフィシャリー)、およびネットワークのシンクアカウントの間で分配されます。

このチュートリアルでは、任意のブロックの報酬を照会し、レシートを使用してアカウント間の分配の内訳を確認する方法を説明します。

前提条件⚓︎

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

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

完全なコード⚓︎

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

import json
import os
import urllib.request

from symbolchain.CryptoTypes import PublicKey
from symbolchain.symbol.Network import Address, Network

NODE_URL = os.getenv(
    'NODE_URL', 'https://reference.symboltest.net:3001')
print(f'Using node {NODE_URL}')

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

try:
    # Get the block header
    with urllib.request.urlopen(
            f'{NODE_URL}/blocks/{BLOCK_HEIGHT}') as response:
        block = json.loads(response.read())
    signer = Network.TESTNET.public_key_to_address(
        PublicKey(block['block']['signerPublicKey']))
    beneficiary = block['block']['beneficiaryAddress']
    print(f'Block height: {BLOCK_HEIGHT}')
    print(f'Signer: {signer}')
    beneficiary_b32 = Address.from_decoded_address_hex_string(
        beneficiary)
    print(f'Beneficiary: {beneficiary_b32}')

    # Get the network sink address
    with urllib.request.urlopen(
            f'{NODE_URL}/network/properties') as response:
        properties = json.loads(response.read())
    sink_b32 = properties['chain']['harvestNetworkFeeSinkAddress']
    sink = Address(sink_b32).bytes.hex().upper()
    print(f'Network sink: {sink_b32}')

    # Get the inflation reward at this height
    with urllib.request.urlopen(
            f'{NODE_URL}/network/inflation'
            f'/at/{BLOCK_HEIGHT}') as response:
        inflation = json.loads(response.read())
    reward = int(inflation['rewardAmount'])
    print(f'Inflation reward: {reward / 1e6:,.6f} XYM')

    # Get harvest fee receipts for this block
    with urllib.request.urlopen(
            f'{NODE_URL}/statements/transaction'
            f'?height={BLOCK_HEIGHT}'
            f'&receiptType=8515') as response:
        receipts = json.loads(response.read())

    # Label and display the reward distribution
    total = 0
    print('\nReward distribution:')
    for item in receipts['data']:
        for r in item['statement']['receipts']:
            if r['type'] != 8515:
                continue
            amount = int(r['amount'])
            total += amount
            target = r['targetAddress']
            if target == sink:
                label = 'Network sink (5%)'
            elif target == beneficiary:
                label = 'Beneficiary (25%)'
            else:
                label = 'Harvester'
                print(f'  {label}: {amount / 1e6:,.6f} XYM')
                harvester = Address.from_decoded_address_hex_string(
                    target)
                print(f'  Harvester: {harvester}')
                continue
            print(f'  {label}: {amount / 1e6:,.6f} XYM')

    # Summary
    fees = total - reward
    print('\nSummary:')
    print(f'  Total block reward: {total / 1e6:,.6f} XYM')
    print(f'  Inflation: {reward / 1e6:,.6f} XYM')
    print(f'  Transaction fees: {fees / 1e6:,.6f} XYM')

except Exception as error:
    print(error)

Download source

import { PublicKey } from 'symbol-sdk';
import { Address, Network } from 'symbol-sdk/symbol';

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

const NODE_URL = process.env.NODE_URL ||
    'https://reference.symboltest.net:3001';
console.log(`Using node ${NODE_URL}`);

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

// Get the block header
const block = await (await fetch(
    `${NODE_URL}/blocks/${BLOCK_HEIGHT}`)).json();
const signer = Network.TESTNET.publicKeyToAddress(
    new PublicKey(block.block.signerPublicKey));
const beneficiary = block.block.beneficiaryAddress;
console.log(`Block height: ${BLOCK_HEIGHT}`);
console.log(`Signer: ${signer}`);
const beneficiaryB32 = Address.fromDecodedAddressHexString(
    beneficiary);
console.log(`Beneficiary: ${beneficiaryB32}`);

// Get the network sink address
const properties = await (await fetch(
    `${NODE_URL}/network/properties`)).json();
const sinkB32 = properties.chain.harvestNetworkFeeSinkAddress;
const sink = Array.from(new Address(sinkB32).bytes)
    .map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();
console.log(`Network sink: ${sinkB32}`);

// Get the inflation reward at this height
const inflation = await (await fetch(
    `${NODE_URL}/network/inflation/at/${BLOCK_HEIGHT}`)).json();
const reward = parseInt(inflation.rewardAmount, 10);
console.log(`Inflation reward: ${fmt(reward)} XYM`);

// Get harvest fee receipts for this block
const receipts = await (await fetch(
    `${NODE_URL}/statements/transaction`
    + `?height=${BLOCK_HEIGHT}&receiptType=8515`)).json();

// Label and display the reward distribution
let total = 0;
console.log('\nReward distribution:');
for (const item of receipts.data) {
    for (const r of item.statement.receipts) {
        if (r.type !== 8515) continue;
        const amount = parseInt(r.amount, 10);
        total += amount;
        let label;
        if (r.targetAddress === sink) {
            label = 'Network sink (5%)';
        } else if (r.targetAddress === beneficiary) {
            label = 'Beneficiary (25%)';
        } else {
            label = 'Harvester';
            const harvester = Address.fromDecodedAddressHexString(
                r.targetAddress);
            console.log(`  ${label}: ${fmt(amount)} XYM`);
            console.log(`  Harvester: ${harvester}`);
            continue;
        }
        console.log(`  ${label}: ${fmt(amount)} XYM`);
    }
}

// Summary
const fees = total - reward;
console.log('\nSummary:');
console.log(`  Total block reward: ${fmt(total)} XYM`);
console.log(`  Inflation: ${fmt(reward)} XYM`);
console.log(`  Transaction fees: ${fmt(fees)} XYM`);

Download source

コード解説⚓︎

コードは、ブロックの署名者キー、受益者およびネットワークのシンクアドレスを、インフレーション額とともに取得します。 次に、ブロックのハーベストレシートを照会し、アドレスを比較して各受取人をラベル付けし、消去法によってハーベスターを特定します。 最後に、合計からインフレーションを差し引くことでトランザクション手数料を導き出します。

ブロック情報の取得⚓︎

    # Get the block header
    with urllib.request.urlopen(
            f'{NODE_URL}/blocks/{BLOCK_HEIGHT}') as response:
        block = json.loads(response.read())
    signer = Network.TESTNET.public_key_to_address(
        PublicKey(block['block']['signerPublicKey']))
    beneficiary = block['block']['beneficiaryAddress']
    print(f'Block height: {BLOCK_HEIGHT}')
    print(f'Signer: {signer}')
    beneficiary_b32 = Address.from_decoded_address_hex_string(
        beneficiary)
    print(f'Beneficiary: {beneficiary_b32}')
// Get the block header
const block = await (await fetch(
    `${NODE_URL}/blocks/${BLOCK_HEIGHT}`)).json();
const signer = Network.TESTNET.publicKeyToAddress(
    new PublicKey(block.block.signerPublicKey));
const beneficiary = block.block.beneficiaryAddress;
console.log(`Block height: ${BLOCK_HEIGHT}`);
console.log(`Signer: ${signer}`);
const beneficiaryB32 = Address.fromDecodedAddressHexString(
    beneficiary);
console.log(`Beneficiary: ${beneficiaryB32}`);

このスニペットは、 NODE_URL および BLOCK_HEIGHT 環境変数を使用して、APIノードと対象ブロックを選択し、ブロックヘッダーを取得します。 設定されていない場合は、デフォルトで参照用のテストネットノードとブロック 3222290 が使用されます。

/blocks/{height} GET エンドポイントは、ブロックヘッダーを返します。これには、ノードオペレーターによって指定された signerPublicKey および beneficiaryAddress が含まれます。 受益者アドレスは、後で受益者のレシートを特定するために必要になります。

ネットワークシンクアドレスの取得⚓︎

    # Get the network sink address
    with urllib.request.urlopen(
            f'{NODE_URL}/network/properties') as response:
        properties = json.loads(response.read())
    sink_b32 = properties['chain']['harvestNetworkFeeSinkAddress']
    sink = Address(sink_b32).bytes.hex().upper()
    print(f'Network sink: {sink_b32}')
// Get the network sink address
const properties = await (await fetch(
    `${NODE_URL}/network/properties`)).json();
const sinkB32 = properties.chain.harvestNetworkFeeSinkAddress;
const sink = Array.from(new Address(sinkB32).bytes)
    .map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();
console.log(`Network sink: ${sinkB32}`);

harvestNetworkFeeSinkAddress/network/properties GET から取得され、後でシンクレシートを特定するために必要になります。 このプロパティは base32 形式であり、レシートのアドレスは16進数エンコードされているため、比較のために SDK の Address クラスを使用して16進数に変換します。

インフレーション報酬の取得⚓︎

    # Get the inflation reward at this height
    with urllib.request.urlopen(
            f'{NODE_URL}/network/inflation'
            f'/at/{BLOCK_HEIGHT}') as response:
        inflation = json.loads(response.read())
    reward = int(inflation['rewardAmount'])
    print(f'Inflation reward: {reward / 1e6:,.6f} XYM')
// Get the inflation reward at this height
const inflation = await (await fetch(
    `${NODE_URL}/network/inflation/at/${BLOCK_HEIGHT}`)).json();
const reward = parseInt(inflation.rewardAmount, 10);
console.log(`Inflation reward: ${fmt(reward)} XYM`);

/network/inflation/at/{height} GET エンドポイントは、指定されたブロック高におけるインフレーション報酬額を返します。 この値は絶対単位であるため、XYM(可分性 6)の場合、 113474978 絶対単位は 113.474978 ユニット単位を表します。

インフレーションスケジュールはネットワーク設定で定義されており、時間の経過とともに減少します。

報酬の分配の照会⚓︎

    # Get harvest fee receipts for this block
    with urllib.request.urlopen(
            f'{NODE_URL}/statements/transaction'
            f'?height={BLOCK_HEIGHT}'
            f'&receiptType=8515') as response:
        receipts = json.loads(response.read())

    # Label and display the reward distribution
    total = 0
    print('\nReward distribution:')
    for item in receipts['data']:
        for r in item['statement']['receipts']:
            if r['type'] != 8515:
                continue
            amount = int(r['amount'])
            total += amount
            target = r['targetAddress']
            if target == sink:
                label = 'Network sink (5%)'
            elif target == beneficiary:
                label = 'Beneficiary (25%)'
            else:
                label = 'Harvester'
                print(f'  {label}: {amount / 1e6:,.6f} XYM')
                harvester = Address.from_decoded_address_hex_string(
                    target)
                print(f'  Harvester: {harvester}')
                continue
            print(f'  {label}: {amount / 1e6:,.6f} XYM')
// Get harvest fee receipts for this block
const receipts = await (await fetch(
    `${NODE_URL}/statements/transaction`
    + `?height=${BLOCK_HEIGHT}&receiptType=8515`)).json();

// Label and display the reward distribution
let total = 0;
console.log('\nReward distribution:');
for (const item of receipts.data) {
    for (const r of item.statement.receipts) {
        if (r.type !== 8515) continue;
        const amount = parseInt(r.amount, 10);
        total += amount;
        let label;
        if (r.targetAddress === sink) {
            label = 'Network sink (5%)';
        } else if (r.targetAddress === beneficiary) {
            label = 'Beneficiary (25%)';
        } else {
            label = 'Harvester';
            const harvester = Address.fromDecodedAddressHexString(
                r.targetAddress);
            console.log(`  ${label}: ${fmt(amount)} XYM`);
            console.log(`  Harvester: ${harvester}`);
            continue;
        }
        console.log(`  ${label}: ${fmt(amount)} XYM`);
    }
}

合計報酬(インフレーション + トランザクション手数料)がどのように分配されたかを確認するため、コードは receiptType=8515Harvest_Fee )でフィルタリングして /statements/transaction GET エンドポイントを照会します。これにより、各参加者がブロックのハーベストに対して受け取った正確な金額が返されます。

メモ: レシートタイプのフィルター

receiptType パラメータは、そのタイプのレシートを少なくとも1つ含むステートメントをフィルタリングしますが、各ステートメントには他のタイプのレシートも含まれている場合があります。 コードは、同じステートメント内のハーベスト以外のレシートをスキップします。

各レシートの targetAddress を受益者およびシンクアドレスと比較して、各受取人にラベルを付けます。 残ったアドレスがハーベスターです。

ハーベスターと受益者が同じアカウントである場合、ネットワークは受益者の取り分をスキップし、ハーベスターが残りの全額を受け取ります。 その場合、作成されるレシートは3つではなく2つ(ハーベスター + シンク)になります。

情報: ブロックの signerPublicKey を使用しない理由

ハーベスターは通常、メインキーではなくリモートキーを使用してブロックに署名します。 その場合、ブロックの signerPublicKey はハーベスターのメインアドレスとは対応しません。 リモートキーは、署名者キーを使用して /accounts/{accountId} GET を照会し、その supplementalPublicKeys.linked.publicKey をメインアカウントまでたどることで解決できます。 ただし、このリンクは現在のアカウント状態を反映しており、ブロックがハーベストされてから変更されている可能性があります。 信頼できる情報源はレシートです。

すべての Harvest_Fee レシートの合計は、ブロック報酬の合計(インフレーション + トランザクション手数料)と等しくなります。

手数料の内訳の計算⚓︎

    # Summary
    fees = total - reward
    print('\nSummary:')
    print(f'  Total block reward: {total / 1e6:,.6f} XYM')
    print(f'  Inflation: {reward / 1e6:,.6f} XYM')
    print(f'  Transaction fees: {fees / 1e6:,.6f} XYM')
// Summary
const fees = total - reward;
console.log('\nSummary:');
console.log(`  Total block reward: ${fmt(total)} XYM`);
console.log(`  Inflation: ${fmt(reward)} XYM`);
console.log(`  Transaction fees: ${fmt(fees)} XYM`);

最後に、ブロック報酬の合計からインフレーション額を差し引くことで、ブロックで収集されたトランザクション手数料が求められます。 表示のために、すべての値は絶対単位からユニット単位に変換されます。

あるいは、各トランザクションの有効な手数料(バイト単位のサイズにブロックヘッダーの feeMultiplier を掛けたもの)を合計することで、手数料の合計を計算することもできます。

出力⚓︎

以下の出力は、ブロック 3,222,290 の報酬を照会する典型的な実行例を示しています。

Using node https://reference.symboltest.net:3001
Block height: 3222290
Signer: TAEJLGM3HFHSWEBISOOPGHFQYENKKGTNPZR4MGQ
Beneficiary: TACLHA4QM3AHTVYMD4ON5BHRMA2E5P5EXRETKBY
Network sink: TBC3AX4TMSYWTCWR6LDHPKWQQL7KPCOMHECN2II
Inflation reward: 113.474978 XYM

Reward distribution:
  Harvester: 79.455446 XYM
  Harvester: TCOADQ4OCZYUF3SVIC6LYLSMQI2DWJIF4DU4EHA
  Network sink (5%): 5.675388 XYM
  Beneficiary (25%): 28.376944 XYM

Summary:
  Total block reward: 113.507778 XYM
  Inflation: 113.474978 XYM
  Transaction fees: 0.032800 XYM

出力の主なポイント:⚓︎

  • アドレス (3-5行目): 署名者アドレスは、ブロックの signerPublicKey から派生します。 受益者はノードオペレーターによって設定され、ネットワークシンクはネットワーク設定で定義された固定のシステムアカウントです。

  • 報酬の分配 (9-12行目): ブロック報酬合計に対する各参加者の取り分。 ハーベスタは、シンクでも受益者でもないレシートの宛先として、消去法によって特定されます。 ハーベスタはブロックの署名にリモートキーを使用するため、署名者アドレス(3行目)がハーベスタのアドレス(10行目)と異なることに注意してください。

  • 概要 (15-17行目): ブロック報酬の合計は、すべての Harvest_Fee レシートの合計です。 インフレーション部分はネットワーク設定から得られ、トランザクション手数料(0.032800 XYM)は合計報酬とインフレーションの差額です。

結論⚓︎

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

ステップ 関連ドキュメント
ブロック情報の取得 /blocks/{height} GET
ネットワークシンクアドレスの取得 /network/properties GET
インフレーション報酬の取得 /network/inflation/at/{height} GET
報酬の分配の照会 /statements/transaction GET