Specification for the Rewards Merkle Tree
Specification for the Rewards Merkle Tree
Rocket Pool’s Redstone update introduced a new rewards system based on a Merkle Tree distribution system. For each rewards interval, this tree will capture the RPL rewards (both for node operation and Oracle DAO membership) and the ETH rewards (from the Smoothing Pool, if relevant) for all of the nodes in the Rocket Pool network.
In the Houston update, following RPIP-53 the tree was modified slightly to account for the new RPL withdrawal address feature.
The following is a specification that formally describes the tree’s construction and behavior for rewards specification version 10 and higher. Users should be able to use this specification to independently verify the tree that is produced by the Oracle DAO.
Overall Structure
Tree Size and Leaf Nodes
The Rewards Merkle Tree is a complete binary tree, where each node has exactly two children (with the exception of leaf nodes, which have zero). Therefore, the total size of the tree is always a power of 2, minus 1.
Fundamental to each Merkle Tree is the data that is used to produce the leaf nodes of the tree. Leaf nodes correspond to unique Execution layer addresses, called claimers. They can represent any address; they do not specifically require the address to be a registered Rocket Pool node address. For each unique address in the tree, there will be exactly one corresponding leaf node. There will not be multiple leaf nodes corresponding to the same address.
The value of the leaf node will be the Keccak256
hash of the claimer’s metadata (described further below).
If the total number of tree nodes is not a power of 2, all of the remaining leaf nodes will have the null hash (0x0000…0000) instead of the hashed node metadata to pad the total count to a power of 2. For example, if there were 9 claimers, they would each constitute a leaf node of the tree and there would be 7 supplemental “null” leaf nodes to bring the total leaf count to 16 (the next highest power of 2).
Sorting Leaf Nodes
Prior to tree generation, claimers must be determined and their metadata must be hashed to produce the values of the corresponding leaf nodes. This process is described in the Leaf Node Values section below.
The hashes should be recorded in big-endian format. This collection must then be sorted lexicographically by these hash values, so the leaf node with the lowest value becomes the leftmost (first) leaf node. The leaf node with the second-lowest value must be the second leaf node, and so on.
Missing leaf nodes filled in with null hash values (0x0000...0000
) will be used to pad the tree at the end of this sorted list, not before it.
This will cause those leaf nodes to be out of order lexicographically (as 0x0000...0000
would normally come before anything else).
For example, if there were 9 claimers that generated the following leaf node values from their hashed metadata:
0xb50d..., 0x213f..., 0xb000..., 0x2c23..., 0x2bc1..., 0x5efe..., 0xf4d7..., 0x91b2..., 0xe5ed...
These leaf nodes would be sorted into the following order prior to generating the tree (including the null-hash padding at the end):
0x213f..., 0x2bc1..., 0x2c23..., 0x5efe..., 0x91b2..., 0xb000..., 0xb50d..., 0xe5ed..., 0xf4d7..., 0x0000...., 0x0000...., 0x0000...., 0x0000...., 0x0000...., 0x0000...., 0x0000....
Branch Nodes
To find the value of the branch node, use the following procedure:
- Get the value of the left child node (which is a
Keccak256
hash). Call thisleft
. - Get the value of the right child node (which is a
Keccak256
hash). Call thisright
. - Sort these values lexicographically; that is, if
right
is lower thanleft
, switchleft
andright
.- NOTE: don’t reorder the child nodes in the tree itself, just switch the values of the
left
andright
local variables.
- NOTE: don’t reorder the child nodes in the tree itself, just switch the values of the
- Concatenate the two hashes (which are both 32 bytes) into a 64-byte array where
left
represents the first 32 bytes andright
represents the last 32 bytes. - Calculate the
Keccak256
hash of this 64-byte array. This hash is the value of this branch node.
Consider the following example:
left = 0xfdbbe597834a953e4c4e50fd8ae8859fd4ae6bf808eb72139ee5a4c224e695f9
right = 0xad1d32ebc492ff5ad2ab148049de34db5b6d45b9d467823470dffb4c18a4a337
In this case, the concatenated byte array would be 0xad1d32ebc492ff5ad2ab148049de34db5b6d45b9d467823470dffb4c18a4a337fdbbe597834a953e4c4e50fd8ae8859fd4ae6bf808eb72139ee5a4c224e695f9
(note the reordering due to the sorting here).
The Keccak256
hash of this would be 0xb079b0168e5beba73f17c52b76a614539b242d8efcf6bb99e0dd66a2e251e9e7
, which becomes the value of this branch node.
Leaf Node Values
Start by determining the list of claimers following the rewards calculation specification.
Each leaf node of the tree will be the the Keccak256
hash of a single claimer’s metadata.
The structure for this metadata is as follows:
metadata = address[20] ++ network[32] ++ totalRPL[32] ++ totalETH[32]
where address[20]
refers to a byte array of size 20 bytes, and the ++
operator refers to byte array concatenation.
Each of these values is defined as follows:
address
is the standard 20-byte Ethereum address of the claimer.network
is a 256-bit (32-byte) unsigned integer representing which Rocket Pool network the rewards will be available on.0
represents Mainnet.- More values will be added as we integrate support for various L2s to the rewards system. Currently,
0
is the only legal value for this.
totalRPL
is a 256-bit (32-byte) unsigned integer representing how much RPL the claimer is eligible to claim for this rewards period.- This value is the sum of the RPL rewards provided for staking minipools and the RPL rewards provided for ODAO membership.
- The RPL value is expected in wei, so rewards of
2
RPL would be represented as2000000000000000000
(in decimal).
totalETH
is a 256-bit (32-byte) unsigned integer representing how much ETH is eligible to claim from the Smoothing Pool for this rewards period.- The ETH value is expected in wei, so rewards of
2
ETH would be represented as2000000000000000000
(in decimal).
- The ETH value is expected in wei, so rewards of
In total, the metadata will be an array of 116 bytes.
For example, say there were a claimer with the following values in its metadata:
- Address:
0x8B0EF9f1932A2e44c3D27bE4C70C3BC07A6A27B3
- Network:
0
- Total RPL: 1.5 RPL (
1500000000000000000
in wei which is14d1120d7b160000
in hexadecimal) - Total ETH: 2.5 ETH (
2500000000000000000
in wei which is22b1c8c1227a0000
in hexadecimal)
It would have the following metadata byte array (in hexadecimal):
0x8b0ef9f1932a2e44c3d27be4c70c3bc07a6a27b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014d1120d7b16000000000000000000000000000000000000000000000000000022b1c8c1227a0000
Taking the Keccak256
hash of this value will produce the value of the corresponding leaf node in the tree for this Rocket Pool node.
Complete Example
To do a complete example of the tree structure, consider the following 9 claimers where the RPL and ETH values are already provided as decimal strings in wei
:
"0x14cb2253a2F9898EFA43b9ca15bCFDE401CCFbe7": {
"network": 0,
"amountRPL": "2000000000000000000",
"amountETH": "0"
},
"0x18A58E43c37DdC9ccCf3AC642c6f430ad663E400": {
"network": 0,
"amountRPL": "1333000000000000000",
"amountETH": "300000000000000000"
},
"0x24fBeD7Ecd625D3f0FD19a6c9113DEd436172294": {
"network": 0,
"amountRPL": "2000000000000000000",
"amountETH": "1000000000000000000"
},
"0x6f10Fd508321D27D8F19CBCC2F2f3d5527B637eC": {
"network": 0,
"amountRPL": "100000000000000000",
"amountETH": "300000000000000000"
},
"0x7e9eA400443957BE3918acfbd1f57cF6D3f5126A": {
"network": 0,
"amountRPL": "1000000000000000000",
"amountETH": "0"
},
"0x822Eaeebb9e106C8CB263bDa6455430fEC652653": {
"network": 0,
"amountRPL": "0",
"amountETH": "2000000000000000000"
},
"0x8B0EF9f1932A2e44c3D27bE4C70C3BC07A6A27B3": {
"network": 0,
"amountRPL": "1500000000000000000",
"amountETH": "2500000000000000000"
},
"0xD57D9c08926E28fC9F1f3dd65Db02e6a7958380c": {
"network": 0,
"amountRPL": "100000000000000000",
"amountETH": "300000000000000000"
},
"0xe6ED92D26573c67AF5eca7fB2a49A807FB8f88dB": {
"network": 0,
"amountRPL": "1333000000000000000",
"amountETH": "300000000000000000"
}
These leaf nodes would generate the following tree according to the specification described here: