This post is meant to describe an interesting issue we encountered when implementing the new Merkle Tree-based rewards system (“Garlic Bread”) and start a discussion among our experienced community members about solutions.
Originally, the rewards system was designed as follows:
- At each rewards interval, the Oracle DAO nodes would generate a Merkle Tree that enumerated the total RPL and total ETH earnings (for the Smoothing Pool) of each Rocket Pool node.
- This tree would be uploaded independently by the Oracle DAO nodes to IPFS; as each node would generate the same tree (and thus the same hash), this could be done without any coordination. IPFS would be baked into the Smartnode stack as an extra capability.
- The Oracle DAO nodes would upload the Merkle Root they arrived at to the Rocket Pool smart contracts.
- Once 51% of them voted on the same Merkle Root, it would be canonized as the official root of the tree for that rewards period.
- Node Operators would then pull this file from IPFS once available and use it to claim their rewards.
- Optionally, Node Operators could run IPFS instances as well to host those files and contribute to the decentralization of the rewards system.
Upon experimenting with an initial implementation of this design, I ran into what I believe is a major issue with the design: IPFS is not anonymous. In other words, it is relatively easy to retrieve the IP address of anybody hosting (“pinning”) a particular file.
As the Oracle DAO members would be the ones that originally host the Rewards Merkle Tree per period, they will be the only ones with access to that file at first. What this means in practical terms is that this design would make it trivial to find the IP addresses of the Oracle DAO members and, if opted into running an IPFS node for “rehosting” the file, the Rocket Pool node operators as well. This has obvious implications for censorship resistance and the potential for DDOS attacks on both the Oracle DAO and Node Operators alike.
Because of this, I submit that we need to look at other options or workarounds instead of following the original design.
The first solution is the “easiest” fix that keeps the original design intact. If the problem is finding the IP addresses of the Oracle DAO members, enforce some kind of obfuscation, such as a VPN service, that allows them to quickly mask and modify their IP addresses in case of attack.
The problem with this is that it doesn’t scale; malicious actors can consistently determine the new IP addresses to attack (since the nodes must necessarily pin the rewards files) and continue to oppress them every time one of these addresses is updated.
A related issue is how to handle regular Node Operators who want to rehost a file; they will either have to opt into a VPN provider (which will add some cost overhead, require additional documentation, and may hinder validation performance), or expose their IP to the world as a Rocket Pool node operator.
This option removes IPFS and dependence upon the Oracle DAO as the source of the Merkle Tree entirely. Instead, after a rewards checkpoint, every Rocket Pool member’s
watchtower container will essentially run through the same process as the Oracle DAO nodes to generate the entire Merkle Tree from nothing but the chain data they already have for the Execution Layer (eth1) and the Beacon Chain (eth2).
Admittedly, this is the one we’ve spent the most time talking about but it’s not without its faults.
- Every node operator knows they’re generating the tree from scratch; they don’t have to trust the tree that Oracle DAO generated (unless they arrive at a different Merkle Root, in which case we’ll have to have some kind of conflict resolution to determine why this happened)
- No need to share files, so no exposing IP addresses
- Relatively easy to implement, doesn’t add much extra development overhead
- If the user’s Execution client goes offline for more than 128 blocks (~30 minutes) after a checkpoint is hit, they won’t be able to generate the rewards tree anymore since they no longer have the state data for the snapshot block. They would either need temporary access to some kind of archive node such as Alchemy, or would need to use a pre-generated file from somewhere else. Perhaps we could host the file the Oracle DAO produced on the Rocket Pool website as a backup for users in this situation?
- Generating the Merkle Tree is computationally taxing when it comes to the Smoothing Pool, because it has to look at the complete attestation history of every node opted in for the entire interval. In the worst case scenario, this can take hours if active real-time caching isn’t implemented or is lost.
This solution is the same as the original design, but replaces IPFS with a file sharing protocol that preserves anonymity by design and doesn’t expose IP addresses. For this, we would have to investigate something like Freenet as an alternative.
I am not well-versed enough in any of those projects to discuss their merit, but I openly invite community members who are familiar with them to offer suggestions here.
In this solution, the Oracle DAO will create the Rewards Merkle Tree as expected but will only share them with a trusted, centralized endpoint which will host them. For example, this could be the Rocket Pool website or Infura’s own IPFS endpoint. Node Operators would then access this endpoint during a rewards claim (or perhaps it would be baked directly into the Smartnode).
While this is probably the easiest solution, it’s also the most fragile because it’s a centralized option. If that endpoint fails, users can no longer claim rewards unless there are redundant copies of the files hosted elsewhere. Trust of the data isn’t an issue because the Merkle Root is recorded on-chain (so you always know whether or not your tree file is accurate), but file management is and it sours the UX of rewards claiming if users have to go out of their way to find a “mirror” of the file.
Also, it should go without saying that Rocket Pool’s ethos is to maximize decentralization where possible, so this option should only be used as a last resort if all of the others cannot be adopted.
Maybe this isn’t actually a problem. Maybe having the IP addresses of the Oracle DAO nodes and/or Rocket Pool node operators isn’t a concerning factor. In that case, this is all moot and we should continue with the original design.
With that, I will open this topic up for community discussion. Which option do you prefer and why? How do you plan to address the shortcomings? Is there an option I didn’t include that you want to present?
Let’s see what we can come up with!