Skip to content

Querying Block Rewards⚓︎

BEGINNER

Each block on NEM is produced by a single harvester account. The entire reward for harvesting a block comes from the transaction fees collected in that block, and these fees are paid in full to the harvester that produced it.

This tutorial shows how to query any block, identify its harvester, and sum the transaction fees that form the reward.

Prerequisites⚓︎

Before you start, set up your development environment.

This tutorial only reads data from the network. No account or XEM balance is required.

Full Code⚓︎

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

The snippet uses the NODE_URL environment variable to set the NEM API node. If no value is provided, a default testnet node is used.

The BLOCK_HEIGHT environment variable selects which block to query. If not set, it defaults to 661258, a block harvested on testnet.

Code Explanation⚓︎

The code fetches a block by height and derives the harvester address from the block's signer public key. It then sums the fees of every transaction in the block to obtain the total reward.

Fetching Block Information⚓︎

    # 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}`);

The /block/at/public POST endpoint returns information about the block at the requested height, including the list of transactions present in the block.

Identifying the Harvester⚓︎

    # 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}`);

The signer field holds the public key of the account that harvested the block. The method converts this public key into the corresponding testnet address.

The signer is not always the account that earns the reward

With local harvesting, the signer is the harvester account and receives the reward.

With remote harvesting or delegated harvesting, the signer is a remote account, while the reward is paid to the main account.

Summing the Transaction Fees⚓︎

    # 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`);
    }

Each transaction in the block has a fee field expressed in atomic units. XEM has a divisibility of 6, so 350000 atomic units represent 0.350000 XEM.

Adding the fees of every transaction gives the total reward for the block.

Calculating the Total Reward⚓︎

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

The total block reward equals the sum of all transaction fees, paid in full to the harvester. An empty block has no fees, and therefore no reward.

Alternative: Query rewards by account

This tutorial calculates the reward for a specific block by summing the transaction fees it contains.

If you are interested in the rewards earned by a particular account instead, use the /account/harvests GET endpoint. It returns one entry per harvested block, including a totalFee field with the reward earned for that block.

The endpoint accepts the address of the harvester account, the account that earns the reward. With remote harvesting or delegated harvesting, that is the main account, not the remote account that signed.

A remote account address also returns the blocks it signed on behalf of the main account.

Output⚓︎

The following output shows a typical run querying the rewards for block 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

Some highlights from the output:

  • Harvester (line 4): The address derived from the block's signer public key.
  • Transaction fees (lines 7 to 8): The fee paid by each transaction included in the block.
  • Total block reward (line 10): The sum of all transaction fees paid in full to the harvester.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Fetch block information /block/at/public POST