GitHub Logo HIP-1064: Daily Rewards For Active Nodes

Author Neeharika Sompalli
Working Group Leemon Baird, Richard Bair, Michael Tinker
Requested By Hashgraph
Discussions-To https://github.com/hiero-ledger/hiero-improvement-proposals/discussions/1063
Status Accepted
Needs Council Approval Yes
Needs Hiero Review Yes
Review period ends Wed, 11 Dec 2024 07:00:00 +0000
Type Standards Track
Category Core
Created 2024-10-17
Updated 2025-03-27

Abstract

This HIP proposes a reward mechanism that will incentivize nodes to remain active on the network. The definition of active node is described below. The minimum reward per day for nodes will be a configurable property decided by the governance of the deployed network. This minimum reward may be adjusted based on fees already paid throughout the day. It also introduces a mechanism for individual nodes to elect to enable or disable rewards.

Terminology

Judge
An event that wins the election to be made a judge. It must be a witness, and it will have tended to have been gossiped to most of the other nodes quickly (otherwise it would have lost the election). An event reaches consensus when it is an ancestor of all judges in a given round. The first round where that happens is its consensus round. It’s a math theorem that every round is guaranteed to have at least one judge, and a math conjecture that every round is guaranteed to have judges created by a supermajority of nodes (>2/3 of weight).
Active Node
A node is an *Active Node* if it creates a "judge" in a significant fraction of rounds during a staking period, for example 80%. Any node that is trustworthy and submits events will create judges. If the node doesn’t submit events or when other nodes don’t build on this node’s events, the node will not create judges.
Staking Period
Staking rewards are currently recalculated once per day, though this duration is a setting called the "staking period". Node rewards will be transferred on the same schedule. Everywhere that this document mentions a "day", it should be interpreted as the staking period. And every time calculations about a fraction of rounds are done, they are the average over a single staking period.
Node
A node is a computer actively participating in the network's consensus algorithm.

Motivation

The goal is to give a strong incentive for node operators to keep them active and properly configured throughout every staking period.

Rationale

Clearly defining the criteria for node activity, we can ensure that rewards are distributed fairly, incentivizing nodes to remain active and contribute to the network’s stability and growth.

User Stories

The implementation will be tested under various scenarios, including:

  • As a node operator, I expect to receive at least a minimum node reward when the node is active for at least nodes.activeRoundsPercent rounds, if the node rewards account has a sufficient balance to cover the minimum reward for active node.
  • As a node operator, I do not expect to receive any node rewards when the node is not active for at least nodes.activeRoundsPercent rounds
  • As a network operator, when nodes.adjustNodeFees is set to true, I expect each active node reward to be reduced by the average node fees already collected during that period.
  • As a network operator, when nodes.adjustNodeFees is set to false, I do not expect each node reward to be reduced by the average node fees already collected during that period.
  • As a network operator, I expect each active node to receive at least a minimum reward defined by nodes.minNodeReward , if the node rewards account has a sufficient balance to cover the minimum reward for all active nodes.
  • As a user of the network, I expect that non-active nodes do not receive any rewards
  • As a user of the network, I expect to see all the transaction fees except node fees redirected to the node rewards account when the balance of the node rewards account is below nodes.nodeRewardAccountThreshold and nodes.preserveMinNodeRewardBalance is set to true.
  • As a node operator, I expect to be able to decline node rewards by setting the decline_reward field to true in the NodeUpdate or NodeCreate transaction.

Specification

There will be some configurable properties added.

  • nodes.minPerPeriodNodeRewardUsd : A minimum daily node reward amount in USD. Each node SHALL receive at least this many tinybar each day that the node is not “Active”. The reward SHALL be paid only if the node rewards account has enough balances to cover the minimum reward for all active nodes. This is set to 0 USD by default.
  • nodes.targetYearlyNodeRewardsUsd : A target yearly node reward amount in USD. This is the target amount of USD that is distributed to all active nodes in nodes.numPeriodsToTargetUsd. This is set to 25_000 USD by default.
  • nodes.numPeriodsToTargetUsd : A number of reward distribution periods per year, typically daily. This is set to 365 by default.
  • nodes.nodeRewardAccountThreshold : The minimum balance (in tinybars) required for the node rewards account. When the balance falls below this threshold, all transaction fees (except node fees) are redirected to replenish this account. This is set to 100_000_000_000_000 tinybars (1M hbars) by default.
  • nodes.preserveMinNodeRewardBalance : A flag to preserve the minimum node reward balance. If this is set, the network SHALL redirect transaction fees to replenish the node rewards account if the balance is below nodes.nodeRewardAccountThreshold. This is set to true by default.
  • nodes.adjustNodeFees : A flag to adjust node fees. If this is set, the network SHALL reduce the rewards given for a staking period by the average node fees already collected during that period. This is set to false by default.
  • nodes.activeRoundsPercent : A percentage value relating to active nodes. A node MUST create a judge in at least this percentage of rounds to be considered active. This is set to 10% by default.

The reward SHALL be adjusted based on average fees paid for all nodes when staking.fees.adjustNodeFees is true. Each active node is paid the constant reward minus the average node fees received that day. This reduction is averaged across all nodes; the network SHALL NOT subtract the node fees earned by one node in particular.

The node rewards SHALL be distributed at the end of a staking period using synthetic CryptoTransfer transactions from the accounts.nodeRewardAccount to each node account, that has chosen to accept node rewards.

When the node rewards account has a balance below nodes.nodeRewardAccountThreshold, the network SHALL redirect transaction fees (except node fees) to the node rewards account. This is done to ensure that the node rewards account has enough balance to cover the minimum reward for all active nodes. The network SHALL redirect transaction fees to the node rewards account only if nodes.preserveMinNodeRewardBalance is set to true.

This HIP modifies the following transaction bodies.

  • Each node operator can choose to decline rewards. This can be done using a NodeUpdate or during a NodeCreate transaction. The node operator can set the decline_reward field to true. This will prevent the node from receiving any rewards.
message NodeCreateTransactionBody {
    ....
    /**
     * A boolean flag indicating whether the node operator declines to receive
     * node rewards.
     * <p>
     * If this flag is set to `true`, the node operator declines to receive
     * node rewards.<br/>
     */
    bool decline_reward = 8;
}
message NodeUpdateTransactionBody {
    ....
    /**
     * A boolean indicating that this node has chosen to decline node rewards
     * distributed at the end of staking period.
     * <p>
     * This node SHALL NOT receive reward if this value is set, and `true`.
     */
    google.protobuf.BoolValue decline_reward = 9;

}

This HIP modifies the following state protobufs.

  • Currently, PlatformState’s ConsensusSnapshot contains information about the judge_hashes. But, it doesn’t provide the creator ids. We will deprecate the existing judge_hashes and add a list of judges. Each Judge will include the creator node id and the judge hash for that node. Any node that didn’t create a judge in a round will not be present in the list.
/**
 * A consensus snapshot.<br/>
 * This is a snapshot of the consensus state for a particular round.
 *
 * This message SHALL record consensus data necessary for restart
 * and reconnect.
 */
message ConsensusSnapshot {
    ....
    /**
     * A list of SHA-384 hash values.<br/>
     * The hashes of all judges for this round.
     * <p>
     * This list SHALL be ordered by creator ID.<br/>
     * This list MUST be deterministically ordered.
     */
    repeated bytes judge_hashes = 2 [deprecated=true];
    ....
    /*
     * A list of judge creator ids and its hashes in a round.<br/>
     */
    repeated JudgeId judge_ids = 6;
}

/**
 * A judge information that includes the creator node ID and the
 * SHA-384 hash value of the judge.
 */
message JudgeId {
    /**
     * The creator node ID who created this judge.
     */
    uint64 creator_id = 1;

    /**
     * SHA-384 hash value of this judge
     */
    bytes judge_hash = 2;
}
  • To store the number of rounds in a staking period and the number of rounds in which each node created judges, a new singleton state NodeRewards will be added to TokenService. This singleton state will be updated at the end of every round.
/**
 * A record of node rewards status.<br/>
 * This is used to record the number of "active" nodes in a staking
 * period based on number of judges each node created in that period.
 * It also records the number of rounds so far in the staking period.
 *
 * A Node SHALL be considered "active" if it produced "judges" according
 * to the consensus algorithm in a percentage of rounds, during the
 * staking period, greater than the network configuration value for
 * `nodes.activeRoundsPercent`.
 */
message NodeRewards {
    /**
     * A number of rounds so far, in this staking period.
     */
    uint64 num_rounds_in_staking_period = 1;

    /**
     * The fees collected by reward-eligible node accounts in this period.
     */
    uint64 fees_collected_by_reward_eligible_nodes = 2;

    /**
     * A list of node activities.<br/>
     * This records the number of rounds when each node created
     * judges for the consensus algorithm.
     * <p>
     * This list SHALL contain one entry for each node participating
     * in consensus during this staking period.
     */
    repeated NodeActivity node_activities = 3;
}

/**
 * A record of judge rounds missed by a single node.<br/>
 * This records, for a single node, the number of rounds so far, during this staking
 * period that missed creating judges. This is used to determine if the node is
 * "active" or not.
 *
 * This message SHALL NOT record the total number of rounds in a staking
 * period.<br/>
 * This message SHALL record a count of rounds for a single node that missed creating judges.
 */
message NodeActivity {
    /**
     * A node identifier.
     */
    uint64 node_id = 1;

    /**
     * A count of rounds.<br/>
     * This is the count of rounds so far, in this staking period in which the node identified
     * by `node_id` did not create judges.
     */
    uint64 num_missed_judge_rounds = 2;
}
  • To store the last node reward payment time, a new field last_node_reward_payment_time will be added to NetworkStakingRewards. This field will be updated at the end of every staking period.
    /**
     * The last time a node reward payment was made. This will be set at the
     * end of a staking period.
     */
    proto.Timestamp last_node_reward_payments_time = 5;
  • Each node operator can decline node rewards. This can be done using a NodeUpdate or during a NodeCreate transaction. The decline_reward field will be added to the Node stored in state.
message Node {
    ....
    /**
     * A flag indicating this node declines node rewards distributed at
     * the end of staking period.
     * <p>
     * If this field is set, then this node SHALL NOT receive any node rewards
     * distributed at the end of the staking period.
     */
    bool decline_reward = 11;
}

Mirror Node

The mirror node REST API will also expose the node’s decline_reward field.

The /api/v1/network/nodes endpoint currently returns an array of "nodes" objects. A new property decline_reward will be added to each node in the "nodes" JSON response object.

An example /api/v1/network/nodes message REST API response with the new property is shown below.

{
  "nodes": [
    {
      "description": "address book 1",
      "file_id": "0.0.102",
      "max_stake": 50000,
      "memo": "0.0.4",
      "min_stake": 1000,
      "node_account_id": "0.0.4",
      "node_cert_hash": "0x01d173753810c0aae794ba72d5443c292e9ff962b01046220dd99f5816422696e0569c977e2f169e1e5688afc8f4aa16",
      "node_id": 1,
      "decline_reward": true,
      "public_key": "0x4a5ad514f0957fa170a676210c9bdbddf3bc9519702cf915fa6767a40463b96f",
      "reward_rate_start": 1000000,
      "service_endpoints": [
        {
          "ip_address_v4": "128.0.0.6",
          "port": 50216
        }
      ],
      "stake": 20000,
      "stake_not_rewarded": 19900,
      "stake_rewarded": 100,
      "staking_period": {
        "from": "1655164800.000000000",
        "to": "1655251200.000000000"
      },
      "timestamp": {
        "from": "187654.000123457",
        "to": null
      }
    }
  ],
  "links": {
    "next": null
  }
}

Backward Compatibility

Existing nodes will not require any changes to their software or configuration to be eligible for rewards, as long as they meet the active criteria.

Security Implications

No known security concerns.

How to Teach This

This HIP incentivizes node operators for keeping the node active by guaranteeing a minimum reward in the periods that they accomplish this. The reward is paid only if the node rewards account has enough balance to cover the minimum reward for all active nodes.

Rejected Ideas

  • We considered using the number of transactions submitted by a node to assess node activity.
    • This was rejected in favor of counting rounds with judges because the latter approach incorporates consensus network assessment of node “honesty” and is more “fair”.

Reference Implementation

Copyright/license

This document is licensed under the Apache License, Version 2.0 – seeLICENSE or (https://www.apache.org/licenses/LICENSE-2.0)

Citation

Please cite this document as: