oDAO Membership Interest - peteris

I am interested in becoming an Oracle DAO member. I’d like to run a node to contribute to Oracle DAO decentralization and diversification, watch over protocol upgrades and it’d allow me to contribute to Rocket Pool full-time.


Contributions

I was convinced The Merge was going to be a huge catalyst for Ethereum. When I came across the Rocket Pool Investment Thesis on /r/cryptocurrency I immediately knew this is what I had been looking for. I sold all my shitcoins and bought RPL. I joined the Discord server and have been following the community since September 2021.

In December 2021, I started building Rocketscan in evenings and weekends. It was going to be like Etherscan but for Rocket Pool. It was going to have killer features such as a unified RPL holders table with old and new RPL as well as Uniswap v3 LP positions, unified rETH holders table across L1 and L2s and a beacon chain integration. While building Rocketscan I became very familiar with the protocol and its smart contracts.

Here’s a list of my contributions to Rocket Pool

Other contributions that went nowhere

Setup

I am also familiar with Smartnode, Docker and Linux in general. When I was 12 I spent a week compiling Linux From Scratch on my Intel Celeron PC.

I’d like to contribute to Oracle DAO client diversity by running less commonly chosen client combinations and platforms. I’d try to do something interesting e.g. run natively on macOS (not in Docker) or Windows or perhaps even FreeBSD. Maybe switch between the platforms based on the season :smiley: I’m thinking Lodestar as CL and Erigon as EL. I’d be happy to be the first to try Reth when it’s ready.

My node would be located in my apartment. I have a fast residential fiber internet connection. I’m based in Switzerland.

Summary

  • Jurisdiction: Switzerland
  • Residential internet connection (fiber)
  • Platform: macOS or Windows
  • Clients: Lodestar and Erigon or Reth
  • Full archive node
  • Fallback on Hetzner or Azure
  • No minipools on this node

Public goods

I’d like to work on an alternative watchtower implementation. One of the most complex parts is treegen which I already have implemented. Patches would also like to build a watchtower implementation which means that there’d be 3 possible options to choose from.

Smartnode has three components: watchower (oDAO stuff), making it easy to run EC & CL clients in Docker and a command line interface for interacting with Rocket Pool smart contracts (registering a node, launchhing a minipool, claiming and staking, etc.). I’d like to build an alternative Smartnode implementation with a user interface similar to Rocketscan. This UI could be an addon to Smartnode and Smartnode would still manage EC & CL clients. One of the killer features would be that the node could be a smart contract such as a Gnosis Safe.

I also have many ideas for Rocketscan features and experiments with Rocket Pool (like getting it to work on Gnosis Chain as a canary network).

Public goods contributions

  • Watchtower alternative implementation
  • Smartnode UI with Gnosis Safe support
  • Rocketscan features

I believe I have good familiarity with the protocol and technical expertise to evaluate contract upgrade proposals and watchtower changes in Smartnode to be able to participate in oDAO governance. I also have the skills to run a node to contribute to oDAO client and platform diversification.

#rocketscanforodao


If you are an oDAO member you can create a proposal to invite me to the oDAO like this (both (mainnet and goerli):

  • ID: rocketscan
  • Node: 0x3Bb6854c0F46Ede02D117a66d808E3Fb17c9a477
  • URL: https://rocketscan.io
rocketpool odao propose member invite 0x3Bb6854c0F46Ede02D117a66d808E3Fb17c9a477 rocketscan https://rocketscan.io
17 Likes

I cannot overstate how much I support peteris’s candidacy.

Rocketscan is a marvel. It is the result of a herculean effort for a team of engineers, and somehow, it was developed by one, on weekends/evenings.

An oDAO seat for rocketscan would check two boxes at once: public goods funding and rocket pool ecosystem funding.

I support this not only as a member of the pDAO but also as a fractional member of the oDAO.

7 Likes

I think Peteris would make a great oDao member. I’m definitely in favor of this application.

4 Likes

Peteris running an oDAO node would be a huge win for decentralization of Rocket Pool. I personally know Peteris to be highly competent, very comitted and his track record speaks for itself.

Having another oDAO node in a neutral and safe jurisdicition (Switzerland) reduces the current concentration of nodes in Five Eyes countries (esp. USA and Australia). With the USA’s Operation Chokepoint and its “regulation by enforcement” approach this becomes very important.

With his strong coding and engineering skills it would be a huge win for our protocol if his oDAO seat leads to him being able to spend more time on Rocket Pool.

6 Likes

I think he’s great with Rocket pool smart contracts, and he’s also great with on-chain data analytics, web programming, and server operations. The fact that he lives in Europe is also a plus.
And he has a very cute cat PFP.

3 Likes

Oracle DAO Proposal #13 - Atlas upgrade

The Oracle DAO proposal to upgrade the contracts for Atlas is now on-chain. It can be voted on by Oracle DAO members in 7 days (April 17, 2023) and if it passes it will be executed by the team on April 18, 2023 at 00:00 UTC.

Here is how this upgrade works:

  • An Oracle DAO member (this time it was rocketpool-2) submits a proposal to add a new contract
  • The upgrade contract address is encoded in the proposal payload
  • There is a 7 day delay before voting starts (April 17)
  • The team can configure the upgrade contract when it’s not locked
  • The team will lock the upgrade contract after the next rewards interval (April 13)
  • The team will verify the upgrade contract and the new versions of contracts on Etherscan
  • Oracle DAO members vote for the proposal
  • If 51% (10 out of 18) of Oracle DAO members vote for it by May 29 it will succeed
  • Succeeded proposal then needs to be executed
  • It will be executed by the team at the official Atlas launch time (April 18 or when the vote reaches the quorum)
  • When the upgrade contract is executed, it will replace the addresses of some Rocket Pool contracts with the new Atlas versions
  • It will also add new contracts, set new settings and also change existing settings

You can determine what exactly will be executed by decoding the payload, getting the upgrade contract address and verifying what the contract at this address does.

You can see the decoded payload of the proposal on Rocketscan:

Or you can verify it yourself:

  1. Grab the payload from the transaction on Etherscan (More details → Decode Input Data)
  2. Go to https://playground.ethers.org
  3. Paste this code and replace the placeholder with the payload
new ethers.utils.Interface(['function proposalUpgrade(string action, string contractName, string contractABI, address contractAddress)']).decodeFunctionData('proposalUpgrade', '0xdfc970ef...')

You should see:

  • Contract name: rocketUpgradeOneDotTwo
  • Contract ABI: eJzVms9P4zoQ...
  • Contract address: 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a (etherscan, beaconcha.in)

The contract ABI is compressed with zlib and base64-encoded. Here is a script to decode the ABI. You can also run it in your browser on replit.

Script to decode the payload and contract ABI
const ethers = require('ethers') // npm install ethers@5
const pako = require('pako') // npm install pako@2

// proposal payload
// https://etherscan.io/tx/0x2495669bf2479617ff09f1399a632893f8c866b4701152c73de80457d0f644d8
const payload = '0xdfc970ef000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000009a0b5d3101d111ea0edd573d45ef2208cc97984a000000000000000000000000000000000000000000000000000000000000000b616464436f6e74726163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016726f636b6574557067726164654f6e65446f7454776f000000000000000000000000000000000000000000000000000000000000000000000000000000000528654a7a566d733950347a6f51782f2b567035343532493664324e78596f5a583277443457396f59514774766a4b7149767152494846713334333539447934616d53524f67422b2b746a582f4e78312b5037526e35357663694c39614e7278656e4e2b31506a31554271353950613179634c6b785a2b41714d2f2b65714e50666f7233315a77524b2f745a5563474679634c417234723631345637327463475a74685855646976326d4839682b654c34395764516550463430486e532b797631544b43334b596731506f4666597451676a31373571544f6777664b7a7a5a51472b71666f6c7a796476724c2f395977332b517450347472657938563370764a4664557869666c305676575049727054796c5376434a5565334f7348745471737479316331624e2b7a4c392b483565636a786364432b6c366d417769394f773452673339364551694b4e564750324c7446664461743277487a3455326d50344c4449483451776b6a43617364366b37356e566850394d70454e6d76525939642b51766a5239674e634736312b6c727534484f54365a6236315759375747376a7163356b38355a5373633033357077544c64774d724645616a67305a44784f59567843464b46737a4e6f43487a644f63583732372f665334732b7171594e5058365033656247734c2f496958322b4d6a63684a4f426f6a32506a4f4e425071544f654875634b2b472b6f505957314c6a6b6f6c69644c634a47494f315756562b744b557131656b633179586465376a6b696b784172586c733962654346427345686c6e4954577a466c36664b4535586f744a4b6d736e304d305352695a525a4b31566952382b4551306a7462684758514a496d4b6a5877595a72497846466f4b414446615a7a4e426e415a6e6363775152786a633037556a6941324553786853544a4468456739584572674b614b5a62663835726e414a506a4c58646a6f784b5458767834684d446b303443734f6e4c79367648462b686a576166496c5044535a6571644e717665785352695a454949734b4a4957646a58454152677545714c6a45673553376c5972345957347249784241684e4257457a2f6677487730326b65315361494768634b4e706c5747477949524949474f57716d6b6876714e2f4c4b76374c374343776d4273795347466e4b526d2b747a755555516d426b2b4635697a4c356d4a387864694553494379314d34344b3934515243594356564961554e4f42337862687373716a3877644872414b4f6f366d34595962496845674a4f494675426b534935714c4d383344436b3478782b7836437945527745704a4d6a5364316478487931675464684e74666e49454641785769314d78396c43597963516a4c6e4e56322b6b7262346b52356e513358514b6370546b644962776769453045694956717a65534a6365376a6632426152434134596c5a6153397842454a6b4c6d4646504d546c396a722f41524b6c76486c7a3144744e7859314f386869457745514a59426c614f65554f314551312b676a75783053496c5447664a52436662746a3077416c566c4277593647515432417372425861427354323647676d5244385143703846434d794f5451466b69566964462b742f6f4b5156434549776b585070386557794d31745a39666439687675502f384a3151626553577a4d332b314335335766723233637a57474e7533662b7a7a396a454a4b4571434e5445385237727a72755076636f354f37417135434f39567333786c475a4863307770614d7837774e57396162647843735a4f6251754e7758487a5239795a376c745865763266314b425258593d000000000000000000000000000000000000000000000000'

// https://github.com/rocket-pool/rocketpool/blob/master/contracts/interface/dao/node/RocketDAONodeTrustedProposalsInterface.sol
const iface = new ethers.utils.Interface([
  'function proposalInvite(string id, string url, address nodeAddress)',
  'function proposalLeave(address nodeAddress)',
  'function proposalKick(address nodeAddress, uint256 rplFine)',
  'function proposalSettingUint(string settingContractName, string settingPath, uint256 value)',
  'function proposalSettingBool(string settingContractName, string settingPath, bool value)',
  'function proposalUpgrade(string type, string name, string contractABI, address contractAddress)'
])

const result = iface.decodeFunctionData(iface.getFunction(payload.substring(0, 10)), payload)

console.log(Object.fromEntries(Object.entries(result).filter(([key]) => isNaN(key)))) // filter out duplicate unnamed args

if (result.type == 'addContract' || result.type == 'upgradeContract') {
  const abi = JSON.parse(pako.inflate(Buffer.from(result.contractABI, 'base64'), { to: 'string' }))
  //console.log(abi) // json format
  console.log(Object.keys(new ethers.utils.Interface(abi).functions))
}

This is likely the source code of the upgrade contract: RocketUpgradeOneDotTwo.sol. I am waiting for the upgrade contract at 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a to be verified on Etherscan before continuing my due dillegence.

1 Like

peteris is one of the most qualified and deserving members in the Rocket Pool community for an oDAO seat. Onboarding him would bring a great ton more confidence and trust in the protocol as a whole.

1 Like

Contract code verification

The team has verified the contracts on Etherscan.

They also published a tool to verify that the contracts on Etherscan match the ones on GitHub. But I don’t trust no tool and carried out the verification manually.

Manual verification

Upgrade contract

This is the upgrade contract from the proposal payload: https://etherscan.io/address/0x9a0b5d3101d111EA0edD573d45ef2208CC97984a#code

The name of the contract is RocketUpgradeOneDotTwo.

There are many Solidity files on the page but I searched for the main one which is RocketUpgradeOneDotTwo.sol.

I copy & pasted the code into a file called RocketUpgradeOneDotTwo.sol.etherscan and manually removed the banner/preamble from the header. It’s only on Etherscan and not on GitHub.

The way contract verification works on Etherscan is that the team uploads the source code of contracts and tells Etherscan which compiler version and settings were used. Etherscan compiles the contract and verifies that the compilation result matches the contract code on Ethereum. I have not verified this myself but trust that Etherscan has done it truthfully. Note that Etherscan is an Oracle DAO member.

I then went to GitHub and looked at the latest commit on the master branch. It’s 4c19daf.

This is the file on GitHub at 4c19daf: RocketUpgradeOneDotTwo.sol

I copy & pasted the contents of the file to RocketUpgradeOneDotTwo.sol.

I then compared the hashsum of these files:

$ sha1sum RocketUpgradeOneDotTwo.sol*
1f873b5fa22def16ded7e027b3de0d69f9f0cfdb  RocketUpgradeOneDotTwo.sol
1f873b5fa22def16ded7e027b3de0d69f9f0cfdb  RocketUpgradeOneDotTwo.sol.etherscan

They match. The source code of the contract on Etherscan matches what’s on GitHub in the v1.2 and master branches at commit 4c19daf.

Other upgraded contracts

The upgrade contract is not locked yet so it could still be changed. However, it will be easy to see what will be changed later.

I switched to the Read Contract tab and looked at all the new contract addresses.

I did not verify the ABIs because they are not used in the Rocket Pool protocol. They are used by the official JavaScript library to fetch the ABIs from the chain instead of bundling them.

Like I did before, I copy & pasted the code from each contract to a file called RocketXXX.sol.etherscan and then created a corresponding one from the GitHub repo called RocketXXX.sol.

Contracts & addresses

Results

$ sha1sum *.sol*
a4876b026c0e03777e1121616856b57a0fe20f0c  RocketDAONodeTrustedSettingsMinipool.sol
a4876b026c0e03777e1121616856b57a0fe20f0c  RocketDAONodeTrustedSettingsMinipool.sol.etherscan
05ef6bb54e501a6bdd3718a387d92fd6e0ca570a  RocketDAOProtocolSettingsDeposit.sol
05ef6bb54e501a6bdd3718a387d92fd6e0ca570a  RocketDAOProtocolSettingsDeposit.sol.etherscan
8fc31c1f80bc242523a19871b2d1e8d48c02a05d  RocketDAOProtocolSettingsMinipool.sol
8fc31c1f80bc242523a19871b2d1e8d48c02a05d  RocketDAOProtocolSettingsMinipool.sol.etherscan
63e6ead093f6801361bb30433cda500b4d533abf  RocketDAOProtocolSettingsNode.sol
63e6ead093f6801361bb30433cda500b4d533abf  RocketDAOProtocolSettingsNode.sol.etherscan
6d35d66760a39c8a682724ba004c03a3ee174b5c  RocketDepositPool.sol
6d35d66760a39c8a682724ba004c03a3ee174b5c  RocketDepositPool.sol.etherscan
38ca25f28886270a260e5c6c8077cad32733d10e  RocketMinipoolBase.sol
38ca25f28886270a260e5c6c8077cad32733d10e  RocketMinipoolBase.sol.etherscan
bca00fd14d453538d7a563d0aca7d252b272bd30  RocketMinipoolBondReducer.sol
bca00fd14d453538d7a563d0aca7d252b272bd30  RocketMinipoolBondReducer.sol.etherscan
b90b08e191f30ac51da00bb0d8decbcbc06bf5e0  RocketMinipoolDelegate.sol
b90b08e191f30ac51da00bb0d8decbcbc06bf5e0  RocketMinipoolDelegate.sol.etherscan
a430381f817db187adb41e9b6759bac341eaf5be  RocketMinipoolFactory.sol
a430381f817db187adb41e9b6759bac341eaf5be  RocketMinipoolFactory.sol.etherscan
c037d34df2969d941d5857ea35d43d79e496a045  RocketMinipoolManager.sol
c037d34df2969d941d5857ea35d43d79e496a045  RocketMinipoolManager.sol.etherscan
2fa1e7d7e69703ae18759bf4afa82a674420c01b  RocketMinipoolQueue.sol
2fa1e7d7e69703ae18759bf4afa82a674420c01b  RocketMinipoolQueue.sol.etherscan
30c14d031e079313f1e048c044d68103309fe1d0  RocketNetworkBalances.sol
30c14d031e079313f1e048c044d68103309fe1d0  RocketNetworkBalances.sol.etherscan
cdf2a398e2ff8f6a55ec7efd5c6c316e34ae4966  RocketNetworkFees.sol
cdf2a398e2ff8f6a55ec7efd5c6c316e34ae4966  RocketNetworkFees.sol.etherscan
ddfbdfbdb7735f3fecf61ae9bf3805eb83d5fdc2  RocketNetworkPrices.sol
ddfbdfbdb7735f3fecf61ae9bf3805eb83d5fdc2  RocketNetworkPrices.sol.etherscan
6295e89d775f1d682fe96258375e1c3fd4b32614  RocketNodeDeposit.sol
6295e89d775f1d682fe96258375e1c3fd4b32614  RocketNodeDeposit.sol.etherscan
4819626b9a9473c6aea889bf23aed5d6e9372c0f  RocketNodeDistributorDelegate.sol
4819626b9a9473c6aea889bf23aed5d6e9372c0f  RocketNodeDistributorDelegate.sol.etherscan
eab58b9ee6c6bf65fa8e5549ae609f2c99af0c87  RocketNodeManager.sol
eab58b9ee6c6bf65fa8e5549ae609f2c99af0c87  RocketNodeManager.sol.etherscan
728d888adab88faaf28d43a75a60ed706f84b580  RocketNodeStaking.sol
728d888adab88faaf28d43a75a60ed706f84b580  RocketNodeStaking.sol.etherscan
51ae263b4e7c860f713924d78ccff34ffa1ac801  RocketRewardsPool.sol
51ae263b4e7c860f713924d78ccff34ffa1ac801  RocketRewardsPool.sol.etherscan
1f873b5fa22def16ded7e027b3de0d69f9f0cfdb  RocketUpgradeOneDotTwo.sol
1f873b5fa22def16ded7e027b3de0d69f9f0cfdb  RocketUpgradeOneDotTwo.sol.etherscan

I am happy to report that the contracts on Etherscan match the ones on GitHub.

Team’s verification tool

I also ran the team’s verification tool.

Instructions
$ git clone https://github.com/rocket-pool/verify-1.2.git
$ cd verify-1.2/

$ git log --oneline | head -n1
de5451d (HEAD -> master, origin/master, origin/HEAD) Add check that upgrade contract is locked

$ cp .env.example .env
$ cat .env
ETH_RPC=http://xxx
NETWORK=mainnet
ETHERSCAN_API_KEY=xxx

# change .gitmodules to clone over HTTPS not SSH
$ cat .gitmodules
[submodule "rocketpool"]
	path = rocketpool
	url = https://github.com/rocket-pool/rocketpool.git
$ git submodule sync

Here is the output:

$ ./verify.sh
Cloning into '/home/user/verify-1.2/rocketpool'...
Submodule path 'rocketpool': checked out '57199268b7aef00f261377136a82c6518e938531'

...
added 992 packages, and audited 993 packages in 15s
...

/home/user/verify-1.2
✔️Verified contract at 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a matches RocketUpgradeOneDotTwo
✔️Verified contract at 0x2FB42FfE2d7dF8381853e96304300c6a5E846905 matches RocketNodeDeposit
✔️Verified contract at 0xA347C391bc8f740CAbA37672157c8aAcD08Ac567 matches RocketMinipoolDelegate
✔️Verified contract at 0x42d4e4B59220dA435A0bd6b5892B90fF50e1D8D4 matches RocketDAOProtocolSettingsMinipool
✔️Verified contract at 0x9e966733e3E9BFA56aF95f762921859417cF6FaA matches RocketMinipoolQueue
✔️Verified contract at 0xDD3f50F8A6CafbE9b31a427582963f465E745AF8 matches RocketDepositPool
✔️Verified contract at 0xac2245BE4C2C1E9752499Bcd34861B761d62fC27 matches RocketDAOProtocolSettingsDeposit
✔️Verified contract at 0x6d010C43d4e96D74C422f2e27370AF48711B49bF matches RocketMinipoolManager
✔️Verified contract at 0x0d8D8f8541B12A0e1194B7CC4b6D954b90AB82ec matches RocketNodeStaking
✔️Verified contract at 0x32778D6bf5b93B89177D328556EeeB35c09f472b matches RocketNodeDistributorDelegate
✔️Verified contract at 0x7B8c48256CaF462670f84c7e849cab216922B8D3 matches RocketMinipoolFactory
✔️Verified contract at 0xf824e2d69dc7e7c073162C2bdE87dA4746d27a0f matches RocketNetworkFees
✔️Verified contract at 0x751826b107672360b764327631cC5764515fFC37 matches RocketNetworkPrices
✔️Verified contract at 0x89F478E6Cc24f052103628f36598D4C14Da3D287 matches RocketNodeManager
✔️Verified contract at 0xE535fA45e12d748393C117C6D8EEBe1a7D124d95 matches RocketDAONodeTrustedSettingsMinipool
✔️Verified contract at 0x17Cf2c5d69E4F222bcaDD86d210FE9dc8BadA60B matches RocketDAOProtocolSettingsNode
✔️Verified contract at 0x07FCaBCbe4ff0d80c2b1eb42855C0131b6cba2F4 matches RocketNetworkBalances
✔️Verified contract at 0xA805d68b61956BC92d556F2bE6d18747adAeEe82 matches RocketRewardsPool
✔️Verified contract at 0x560656C8947564363497E9C78A8BDEff8d3EFF33 matches RocketMinipoolBase
✔️Verified contract at 0xf7aB34C74c02407ed653Ac9128731947187575C0 matches RocketMinipoolBondReducer
❌ Upgrade contract is not locked

Note that 5719926 was the last commit in the v1.2 branch and 4c19daf is the merge commit.

1 Like

I fully support Peteris’s bid for oDAO membership. I use Rocketscan weekly if not daily, it is a public good with great visibility.

Moreover I support a larger oDAO in general to help decentralize the protocol.

Peteris for oDAO 2023!!!

1 Like

Team’s verification tool after locking the upgrade contract

The team has added the most recent rewards period to the upgrade contract and locked it. This means that contract addresses and rewards periods can no longer be changed.

Only two corresponding transactions since my review so my verification results are still valid.

image

Running the verification script by the team now produces the following output:

$ ./verify.sh
...
✔️Verified contract at 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a matches RocketUpgradeOneDotTwo
✔️Verified contract at 0x2FB42FfE2d7dF8381853e96304300c6a5E846905 matches RocketNodeDeposit
✔️Verified contract at 0xA347C391bc8f740CAbA37672157c8aAcD08Ac567 matches RocketMinipoolDelegate
✔️Verified contract at 0x42d4e4B59220dA435A0bd6b5892B90fF50e1D8D4 matches RocketDAOProtocolSettingsMinipool
✔️Verified contract at 0x9e966733e3E9BFA56aF95f762921859417cF6FaA matches RocketMinipoolQueue
✔️Verified contract at 0xDD3f50F8A6CafbE9b31a427582963f465E745AF8 matches RocketDepositPool
✔️Verified contract at 0xac2245BE4C2C1E9752499Bcd34861B761d62fC27 matches RocketDAOProtocolSettingsDeposit
✔️Verified contract at 0x6d010C43d4e96D74C422f2e27370AF48711B49bF matches RocketMinipoolManager
✔️Verified contract at 0x0d8D8f8541B12A0e1194B7CC4b6D954b90AB82ec matches RocketNodeStaking
✔️Verified contract at 0x32778D6bf5b93B89177D328556EeeB35c09f472b matches RocketNodeDistributorDelegate
✔️Verified contract at 0x7B8c48256CaF462670f84c7e849cab216922B8D3 matches RocketMinipoolFactory
✔️Verified contract at 0xf824e2d69dc7e7c073162C2bdE87dA4746d27a0f matches RocketNetworkFees
✔️Verified contract at 0x751826b107672360b764327631cC5764515fFC37 matches RocketNetworkPrices
✔️Verified contract at 0x89F478E6Cc24f052103628f36598D4C14Da3D287 matches RocketNodeManager
✔️Verified contract at 0xE535fA45e12d748393C117C6D8EEBe1a7D124d95 matches RocketDAONodeTrustedSettingsMinipool
✔️Verified contract at 0x17Cf2c5d69E4F222bcaDD86d210FE9dc8BadA60B matches RocketDAOProtocolSettingsNode
✔️Verified contract at 0x07FCaBCbe4ff0d80c2b1eb42855C0131b6cba2F4 matches RocketNetworkBalances
✔️Verified contract at 0xA805d68b61956BC92d556F2bE6d18747adAeEe82 matches RocketRewardsPool
✔️Verified contract at 0x560656C8947564363497E9C78A8BDEff8d3EFF33 matches RocketMinipoolBase
✔️Verified contract at 0xf7aB34C74c02407ed653Ac9128731947187575C0 matches RocketMinipoolBondReducer
✔ Verification successful
1 Like

Bytecode verification

Previously I verified that the source code published on Etherscan is the same as the source available on GitHub. I had to trust that Etherscan performed bytecode verification correctly.

I have now also verified that the bytecode of the contracts on-chain matches the bytecode produced by compiling the source code from GitHub.

Script

Get the source code and compile the contracts:

git clone https://github.com/rocket-pool/rocketpool.git
cd rocketpool
git checkout 4c19daf
npm install
npm run compile

Install dependencies for the verification script:

npm install ethers@5 glob@10 ganache-cli

Fork mainnet and impersonate Rocket Pool’s deployer address:

npx ganache-cli --fork https://cloudflare-eth.com --unlock 0x27e80db1f5a975f4c43c5ec163114e796cdb603d

Create a file called verify.js:

const fs = require('node:fs/promises')
const { glob } = require('glob') // npm install glob@10
const { Contract, ContractFactory } = require('ethers') // npm install ethers@5
const { StaticJsonRpcProvider } = require('@ethersproject/providers')

async function main() {
  const providerUrl = 'http://127.0.0.1:8545'

  const deployer = '0x27e80db1f5a975f4c43c5ec163114e796cdb603d'
  const storageAddress = '0x1d8f8f00cfa6758d7bE78336684788Fb0ee0Fa46'
  const upgradeContractAddress = '0x9a0b5d3101d111EA0edD573d45ef2208CC97984a'

  const upgradeContractABI = [
    'function newRocketNodeDeposit() public view returns (address)',
    'function newRocketMinipoolDelegate() public view returns (address)',
    'function newRocketDAOProtocolSettingsMinipool() public view returns (address)',
    'function newRocketMinipoolQueue() public view returns (address)',
    'function newRocketDepositPool() public view returns (address)',
    'function newRocketDAOProtocolSettingsDeposit() public view returns (address)',
    'function newRocketMinipoolManager() public view returns (address)',
    'function newRocketNodeStaking() public view returns (address)',
    'function newRocketNodeDistributorDelegate() public view returns (address)',
    'function newRocketMinipoolFactory() public view returns (address)',
    'function newRocketNetworkFees() public view returns (address)',
    'function newRocketNetworkPrices() public view returns (address)',
    'function newRocketDAONodeTrustedSettingsMinipool() public view returns (address)',
    'function newRocketNodeManager() public view returns (address)',
    'function newRocketDAOProtocolSettingsNode() public view returns (address)',
    'function newRocketNetworkBalances() public view returns (address)',
    'function newRocketRewardsPool() public view returns (address)',
    'function rocketMinipoolBase() public view returns (address)',
    'function rocketMinipoolBondReducer() public view returns (address)'
  ]

  const provider = new StaticJsonRpcProvider(providerUrl)

  const upgradeContract = new Contract(upgradeContractAddress, upgradeContractABI, provider)

  const contracts = [
    ['RocketUpgradeOneDotTwo', upgradeContractAddress],
    ...(await Promise.all(
      Object.values(upgradeContract.interface.functions)
            .map(async f => [ f.name.replace('new', ''), await upgradeContract[f.name]() ])
    ))
  ]

  for (const [contractName, address] of contracts) {
    const blockchainCode = await provider.getCode(address)

    const artifactName = contractName[0].toUpperCase() + contractName.substring(1) // rocketContract => RocketContract
    const artifactFilename = (await glob(`artifacts/**/${artifactName}.sol/${artifactName}.json`))[0]
    const artifact = JSON.parse(await fs.readFile(artifactFilename, 'utf-8'))

    const factory = ContractFactory.fromSolidity(artifact, provider.getSigner(deployer))
    const deployment =
      artifactName.includes('Delegate') || artifactName.includes('RocketMinipoolBase')
        ? await factory.deploy()
        : await factory.deploy(storageAddress)
    await deployment.deployTransaction.wait()
    let deployedArtifactCode = await provider.getCode(deployment.address)

    if (artifactName.includes('RocketMinipoolBase')) {
      deployedArtifactCode = deployedArtifactCode.replace(
        new RegExp(deployment.address.substring(2).toLowerCase(), 'g'),
        address.substring(2).toLowerCase()
      )
    }

    const match = blockchainCode === deployedArtifactCode
    console.log(match ? '✅' : '❌', artifactName, address)
  }
}

main()

Run the verification script:

node verify.js
✅ RocketUpgradeOneDotTwo 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a
✅ RocketNodeDeposit 0x2FB42FfE2d7dF8381853e96304300c6a5E846905
✅ RocketMinipoolDelegate 0xA347C391bc8f740CAbA37672157c8aAcD08Ac567
✅ RocketDAOProtocolSettingsMinipool 0x42d4e4B59220dA435A0bd6b5892B90fF50e1D8D4
✅ RocketMinipoolQueue 0x9e966733e3E9BFA56aF95f762921859417cF6FaA
✅ RocketDepositPool 0xDD3f50F8A6CafbE9b31a427582963f465E745AF8
✅ RocketDAOProtocolSettingsDeposit 0xac2245BE4C2C1E9752499Bcd34861B761d62fC27
✅ RocketMinipoolManager 0x6d010C43d4e96D74C422f2e27370AF48711B49bF
✅ RocketNodeStaking 0x0d8D8f8541B12A0e1194B7CC4b6D954b90AB82ec
✅ RocketNodeDistributorDelegate 0x32778D6bf5b93B89177D328556EeeB35c09f472b
✅ RocketMinipoolFactory 0x7B8c48256CaF462670f84c7e849cab216922B8D3
✅ RocketNetworkFees 0xf824e2d69dc7e7c073162C2bdE87dA4746d27a0f
✅ RocketNetworkPrices 0x751826b107672360b764327631cC5764515fFC37
✅ RocketDAONodeTrustedSettingsMinipool 0xE535fA45e12d748393C117C6D8EEBe1a7D124d95
✅ RocketNodeManager 0x89F478E6Cc24f052103628f36598D4C14Da3D287
✅ RocketDAOProtocolSettingsNode 0x17Cf2c5d69E4F222bcaDD86d210FE9dc8BadA60B
✅ RocketNetworkBalances 0x07FCaBCbe4ff0d80c2b1eb42855C0131b6cba2F4
✅ RocketRewardsPool 0xA805d68b61956BC92d556F2bE6d18747adAeEe82
✅ RocketMinipoolBase 0x560656C8947564363497E9C78A8BDEff8d3EFF33
✅ RocketMinipoolBondReducer 0xf7aB34C74c02407ed653Ac9128731947187575C0
Explanation

If you compare the bytecode you get from getCode to the bytecode from the artifacts generated by the Solidity compiler you’ll find that they sometimes don’t match.

const match = blockchainCode === artifact.deployedBytecode
❌ RocketUpgradeOneDotTwo 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a
❌ RocketDepositPool 0xDD3f50F8A6CafbE9b31a427582963f465E745AF8
❌ RocketNodeDistributorDelegate 0x32778D6bf5b93B89177D328556EeeB35c09f472b
❌ RocketMinipoolBase 0x560656C8947564363497E9C78A8BDEff8d3EFF33

These contracts have immutable fields whose values are not known until the contract is deployed.

One way to solve this would be to check the transactions in which the contracts were deployed but you need to somehow index them or manually look them up.

I decided to try deploying the compiled contracts to a local mainnet fork and then compare their getCode results.

It almost worked.

❌ RocketUpgradeOneDotTwo 0x9a0b5d3101d111EA0edD573d45ef2208CC97984a
❌ RocketMinipoolBase 0x560656C8947564363497E9C78A8BDEff8d3EFF33

RocketUpgradeOneDotTwo saves the deployer of the contract in an immutable variable:

contract RocketUpgradeOneDotTwo {
  address immutable deployer;

  constructor(RocketStorageInterface _rocketStorageAddress) {
    deployer = msg.sender;
  }
}

Luckily ganache-cli lets you impersonate any address in your fork so I could unlock and use the Rocket Pool’s deployer like this:

ganache-cli --unlock 0x27e80db1f5a975f4c43c5ec163114e796cdb603d
const deployer = '0x27e80db1f5a975f4c43c5ec163114e796cdb603d'
const factory = ContractFactory.fromSolidity(artifact, provider.getSigner(deployer))

RocketMinipoolBase was tricker because it stores its own address:

contract RocketMinipoolBase {
  address immutable self;

  constructor() {
    self = address(this);
  }
}

To do the verification exactly I’d need to fork mainnet earlier, deploy all contracts in the same order and then I should be able to get the exact same address during deployment.

However, I decided to simply try replacing the address of my newly deployed contract with the real contract address and it worked:

if (artifactName.includes('RocketMinipoolBase')) {
  deployedArtifactCode = deployedArtifactCode.replace(
    new RegExp(deployment.address.substring(2).toLowerCase(), 'g'),
    address.substring(2).toLowerCase()
  )
}

With these two workarounds all contracts match.

4 Likes

This due diligence on smart contracts upgrades is exactly what we want from oDAO members. I’m not sure if any of the current oDAO members besides the team do that.
Good work @peteris and another reason to support your oDAO candidacy.

4 Likes

Monthly reminder about my interest.

1 Like

+1 because peteris rocks

1 Like

Smartnode v1.9.4 upgrade review

Smartnode v1.9.4 includes changes that affect oDAO duties.

Diffs

There are many changes in this release but the one that affects oDAO is changing the implementation of the CalculateAverageFeeAndDistributorShares function. Until the switchover epoch it will be CalculateAverageFeeAndDistributorShares_Legacy (current implementation) but later CalculateAverageFeeAndDistributorShares_New.

The switchover epoch is configurable as NewFeeDistributorCalcEpoch. It’s set to 204900 (May 31 at 04:00:23 UTC) on mainnet and 177900 (May 23 at 06:00:00 UTC) on prater.

Looking at the changes of the implementation (utils/state/node.go in rocketpool-go), it looks like the function was not updated for Atlas. It is now incorrectly calculating the node operator and rETH holder share by splitting it 50/50.

This function calculates DistributorBalanceUserETH for each node. It’s later summed up in DistributorShareTotal which is used in calculating the total ETH that backs up rETH (totalEth) and submitted as network balances by oDAO. It means that this affects the rETH ratio.

Current implementation gives the following values

Calculating network balances for block 17288510...
...
Deposit pool balance: -15913819773845746907261 wei
Node credit balance: 752000000000000000000 wei
Total minipool user balance: 374388165759978836967785 wei
Staking minipool user balance: 309094180493810288698781 wei
Fee distributor user balance: 216917247802446867154 wei
Smoothing pool user balance: 81522877805056915825 wei
rETH contract balance: 3350154109453952956748 wei
rETH token supply: 337395692338266558260918 wei

New implementation:

Getting network state for EL block 17288510, Beacon slot 6467836
...
Deposit pool balance: -15913819773845746907261 wei
Node credit balance: 752000000000000000000 wei
Total minipool user balance: 374388165759978836967785 wei
Staking minipool user balance: 309094180493810288698781 wei
Fee distributor user balance: 225985992783436959741 wei
Smoothing pool user balance: 81522877805056915825 wei
rETH contract balance: 3350154109453952956748 wei
rETH token supply: 337395692338266558260918 wei

All values are the same except for the fee distributor user (aka rETH holder) balance.

It’s 225.98599 ETH now vs 216.97 ETH previously. It was underreported by 9.0687 ETH. That’s 0.0025667% of the total reported ETH value. This seems to be no big deal.

Based on this, it seems fine to upgrade to Smartnode v1.9.4.

oDAO has voted to cut its rewards from 15% to 8% of RPL inflation now and 1.5% in a year.

The Oracle DAO voted for RPIP-24 oDAO Charter while the Protocol DAO voted for RPIP-25 RPL Inflation Allocation and RPIP-23 pDAO charter. These three RPIPs are interlinked which means that all of them had to pass which they did. oDAO’s vote for RPIP-24 was also an indirect vote for RPIP-25.

Here’s the RPL inflation allocation for the next 14 rewards periods (1 year) with the current ETH/RPL and ETH/USD prices for reference.

(my RPL inflation calculation may be slightly, slightly off)

It is now expected that the Protocol DAO guardian (which is controlled by the team) will change the inflation allocation settings since the pDAO vote on Snapshot has passed.

While not explicitly mentioned in RPIP-24, it is expected that 3 out of 4 Oracle DAO nodes operated by the Rocket Pool team will leave the oDAO (RPIP-25 targets 11-15 members, there are now 18 members). The team will now be funded by the pDAO. The Snapshot vote for RPIP-25 was at the same time for amending RPIP-10 pDAO Budget Allocation to send 3584 RPL to the team immediately and 3908 RPL more if they reduce their oDAO seats from 4 to 1 within 28 days or less if they take their time. For more details see RPIP-10 Expense 1.

2 Likes

I’d like

I think I slightly disagree here. RP reducing seat count is encouraged (with an expense for that, etc). But I don’t think we have a strong expectation.

Erigon and Reth archive nodes for oDAO

Oracle DAO nodes are required to have an archive EL node.

Geth and Nethermind archive nodes are not practical. They require >16 TB of disk space. While Erigon and Reth can fit a full archive node in about 2 TB.

Erigon started out as a fork of Geth (called Turbo Geth) a long time ago but has evolved so much that it is a completely different client. It hasn’t been added to Smartnode already because they have plans to overhaul their architecture a couple of times and each time it would require a full resync. It’s also been buggy in the past (due to a bug one oDAO node that was using Erigon incorrectly scrubbed a minipool, also at some point treegen stopped working with Erigon+Lighthouse).

Paradigm’s Reth is the new kid on the block. It’s written in Rust (hence Reth, like Geth stands for Go Ethereum) and focuses on performance. Reth architecture was unethically inspired by Erigon. It’s built by Paradigm which is a VC and has essentially bought a seat at the table. Reth is built by unethical people but it is very good software. It’s currently very alpha and that’s why it hasn’t been added to Smartnode.

I have added both of them to Smartnode. It’s now possible to sync an archive Erigon or Reth node for mainnet and goerli with Smartnode.

Changes:

Instructions how to build my fork of Smartnode:

mkdir rocketpool
cd rocketpool
git clone https://github.com/peteriscan/smartnode.git -b erigon-reth
git clone https://github.com/peteriscan/smartnode-install.git -b erigon-reth

VERSION=$(grep RocketPoolVersion smartnode/shared/version.go | grep -Eo '".+?"' | sed 's/"//g')
docker run -it --rm -v $(pwd)/smartnode:/smartnode -v $(pwd)/go:/go -w /smartnode/rocketpool-cli rocketpool/smartnode-builder:latest go build -buildvcs=false
docker run -it --rm -v $(pwd)/smartnode:/smartnode -v $(pwd)/go:/go rocketpool/smartnode-builder:latest /smartnode/rocketpool/build.sh
docker build -f smartnode/docker/rocketpool-dockerfile -t rocketpool/smartnode:v$VERSION smartnode

cp smartnode/rocketpool-cli/rocketpool-cli ~/bin/rocketpool
cp smartnode-install/install/scripts/start-ec.sh ~/.rocketpool/scripts/start-ec.sh

~/bin/rocketpool s c         # pick Erigon or Reth in the wizard or switch clients in the config
~/bin/rocketpool s v         # verify that Erigon or Reth docker images are used
~/bin/rocketpool s l eth1    # Erigon or Reth logs

Disk space usage:

  • Erigon on Goerli - 800GB
  • Reth on Mainnet - 2.25TB
1 Like

Holesky testnet in Smartnode

I am participating in the Holesky testnet with 5000 validators on my oDAO machine.

Here’s how to use my Smartnode fork on the Holesky testnet. It builds upon my previous Erigon/Reth fork.

Diffs

Prerequisites

curl -fsSL https://get.docker.com | sudo bash
sudo usermod -aG docker $USER && newgrp docker
sudo apt-get install -qq make

Customize paths

export RPBUILD=~/rocketpool-holesky
export RPHOME=~/.rocketpool-holesky
export RPBIN=~/bin/rocketpool-holesky

Build my fork

git clone -b erigon-reth-holesky https://github.com/peteriscan/smartnode.git $RPBUILD/smartnode
git clone -b erigon-reth-holesky https://github.com/peteriscan/smartnode-install.git $RPBUILD/smartnode-install
make -C $RPBUILD/smartnode

cp $RPBUILD/smartnode/rocketpool-cli/rocketpool-cli $RPBIN
$RPBIN service install --no-deps --path $RPHOME --version v1.10.1 --yes
cp $RPBUILD/smartnode-install/install/scripts/start-*.sh $RPHOME/scripts/

Configure it

$RPBIN -c $RPHOME service config
  • Choose the Holešky testnet network
  • Change ETH1 tag e.g. ethereum/client-go:latest for Geth
  • Change ETH2 tag e.g. chainsafe/lodestar:next for Lodestar
  • Disable MEV boost

You can check the logs with

$RPBIN -c $RPHOME service logs eth1 eth2 validator
Quality of life improvements

Since Rocket Pool is not deployed on Holesky at the time of writing this, the node and watchtower containers are useless.

You could disable them like this

cat > $RPHOME/override/node.yml << EOF
version: "3.7"
services:
  node:
    image: alpine
    command: ["sleep", "infinity"]
    init: true
EOF

cat > $RPHOME/override/watchtower.yml << EOF
version: "3.7"
services:
  node:
    image: alpine
    command: ["sleep", "infinity"]
    init: true
EOF
Import validator keys

If your validators are in the genesis you can import them into Smartnode by following these instructions. My fork has modifications to import non-RP validators and not crash since RP is not deployed.

You can see how I generated my keys here: Add peteris keys by peteriscan · Pull Request #7 · rocknet/holesky · GitHub

Build eth2-val-tools if you don’t have them already

git clone https://github.com/protolambda/eth2-val-tools.git $RPBUILD/eth2-val-tools
cat > $RPBUILD/eth2-val-tools/Dockerfile << EOF
FROM golang
WORKDIR /app
COPY . .
RUN go build
ENTRYPOINT ["/app/eth2-val-tools"]
EOF
docker build -t eth2-val-tools $RPBUILD/eth2-val-tools

Generate keys from your mnemonic and import them

MNEMONIC=$(cat mnemonic.txt)

rm -rf $RPBUILD/keystores

docker run --rm -it \
  -v $RPBUILD:/out \
  -u $UID \
  eth2-val-tools keystores \
  --source-min 0 --source-max 10 \
  --source-mnemonic "$MNEMONIC" \
  --out-loc /out/keystores

cp $RPBUILD/keystores/teku-keys/*.json $RPHOME/data/custom-keys/
grep -R . $RPBUILD/keystores/teku-secrets/ | sed 's@.*keystores/teku-secrets/0x@@g' | sed 's/\.txt//g' | sed 's/:/: /g' | sed 's/\(.*\):/\U\1:/' > $RPHOME/data/custom-key-passwords

$RPBIN -c $RPHOME wallet recover --mnemonic "$MNEMONIC"

rm -rf mnemonic.txt $RPBUILD/keystores $RPHOME/data/custom-key-passwords

It took about

  • 1 minute to generate the 5000 keystores
  • almost 3h for Smartnode to recover them
  • 40 minutes for Lodestar to import them
5 Likes