diff --git a/.github/update_data_daily.yml b/.github/update_data_daily.yml new file mode 100644 index 0000000..31bde1d --- /dev/null +++ b/.github/update_data_daily.yml @@ -0,0 +1,45 @@ +name: Update Data Daily + +on: + schedule: + - cron: '30 */12 * * *' + workflow_dispatch: + +jobs: + update_data: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.x + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install requests + + - name: Fetch latest changes + run: | + git fetch origin main + git reset --hard origin/main + + - name: Run Python script + run: python _scripts/collect_data.py + env: + RATED_API_KEY: ${{ secrets.RATED_TOKEN }} + MIGALABS_API_KEY: ${{ secrets.MIGALABS_TOKEN }} + ERROR_REPORT_ENDPOINT: ${{ secrets.GOOGLE_FORM_ERROR_REPORT_URL }} + + - name: Commit and push changes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add * + git diff --quiet && git diff --staged --quiet || (git commit -m "Update client distribution data" && git push https://${GITHUB_TOKEN}@github.com/${{ github.repository }}.git HEAD:${{ github.ref }}) \ No newline at end of file diff --git a/404.md b/404.md new file mode 100644 index 0000000..983f8f6 --- /dev/null +++ b/404.md @@ -0,0 +1,8 @@ +--- +layout: default +title: 404 +permalink: /404 +--- + + +{%- include partials/404.html -%} diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000..7ce2ef7 --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x286905d38C1b7f0ceb452Cddda83aA4Cbe776fd1" + } + } +} \ No newline at end of file diff --git a/_config.yml b/_config.yml index ddd9354..a9ee318 100644 --- a/_config.yml +++ b/_config.yml @@ -18,12 +18,14 @@ redirect_from: # jekyll-redirect-from json: false # Global variables -analytics_tag: | - - + +# Toast +enable_toast: false +toast_msg_id: 1 # must increment when creating a new message +toast_title: Ether Alpha's Gitcoin grant is live! +toast_msg: This round runs through August 29th. If you enjoy this or other tools created, please support to help maintain existing projects and fund new ones." # Best when under 100 characters +toast_link: https://stateofeth.com # Optional, leave blank to omit +toast_link_text: Donate +# August 29, 23:59:59 GMT+0 +toast_expiration: 30000000000 # epoch time in seconds diff --git a/_data/dashboards/content.yml b/_data/dashboards/content.yml new file mode 100644 index 0000000..38c23d4 --- /dev/null +++ b/_data/dashboards/content.yml @@ -0,0 +1,4 @@ +id: dashboardOverview +toc: + - Health + diff --git a/_data/dashboards/health.yml b/_data/dashboards/health.yml new file mode 100644 index 0000000..86ce91e --- /dev/null +++ b/_data/dashboards/health.yml @@ -0,0 +1,201 @@ +## - id: = metric id (page path as camelcase); must remain unchanged, don't change if url updated +## title: = metric name +## link: = link to page; if omitted the button will be disabled +## data: = data file path; if omitted the progress bar will be greyed (marked as data pending) +## health: = health value; if omitted the progress bar will be greyed (marked as data pending) +## icon: = icon name from _data/icons, should match the icon of the parent category in sidebar nav +## disabled: = (optional) true/false; if true the progress bar will be greyed and button disabled + +# Template to copy for new entries +# - id: +# title: +# link: +# data: +# health: +# icon: + + +# Entries are shown in the order listed +- id: consensusClientDiversityNodes + title: Node Consensus Client Diversity + # link: /metrics/consensus-client-diversity-nodes + data: + health: 55 + icon: window_sidebar +- id: consensus-client-diversity-validators + title: Validator Consensus Client Diversity + link: /metrics/consensus-client-diversity-validators + data: + health: 57 + icon: window_sidebar +- id: consensusClientCount + title: Consensus Client Count + # link: /metrics/consensus-client-count + data: + health: 100 + icon: window_sidebar +- id: consensusClientLanguages + title: Consensus Client Languages + # link: /metrics/consensus-client-languages + data: + health: 100 + icon: window_sidebar +- id: executionClientDiversityNodes + title: Node Execution Client Diversity + # link: /metrics/execution-client-diversity-nodes + data: + health: 14 + icon: window_split +- id: executionClientDiversityValidators + title: Validator Execution Client Diversity + # link: /metrics/execution-client-diversity-validators + data: + health: 18 + icon: window_split +- id: executionClientCount + title: Execution Client Count + # link: /metrics/execution-client-count + data: + # health: 100 + icon: window_split +- id: executionClientLanguages + title: Execution Client Languages + # link: /metrics/execution-client-languages + data: + # health: 100 + icon: window_split +- id: entityStakingDiversity + title: Entity Staking Diversity + # link: /metrics/entity-staking-diversity + data: + # health: 51 + icon: people +- id: lstStakingDiversity + title: LST Staking Diversity + # link: /metrics/lst-staking-diversity + data: + # health: 32 + icon: people +- id: lstStakingMarketshare + title: LST Staking Marketshare + # link: /metrics/lst-staking-marketshare + data: + # health: 32 + icon: people +- id: decentralizedLstMarketshare + title: Decentralized LST Marketshare + # link: /metrics/decentralized-lst-marketshare + data: + # health: 32 + icon: people +- id: homeStakerMarketshare + title: Home Staker Marketshare + # link: /metrics/home-staker-marketshare + data: + # health: 32 + icon: people +- id: soloStakerMarketshare + title: Solo Staker Marketshare + # link: /metrics/solo-staker-marketshare + data: + # health: 32 + icon: people +- id: nodeCount + title: Node Count + # link: /metrics/node-count + data: + # health: 80 + icon: nuc +- id: metaClientCount + title: Meta Client Count + # link: /metrics/meta-client-count + data: + # health: 80 + icon: window +- id: geographicDiversityNodes + title: Node Geographic Diversity + # link: /metrics/geographic-diversity-nodes + data: + # health: 61 + icon: globe +- id: geographicDiversityValidators + title: Validator Geographic Diversity + # link: /metrics/geographic-diversity-validators + data: + # health: 52 + icon: globe + +- id: stablecoinDiversity + title: Stablecoin Diversity + # link: /metrics/stablecoin-diversity + data: + icon: coin +- id: decentralizedStablecoinMarketshare + title: Decentralized Stablecoin Marketshare + # link: /metrics/decentralized-stablecoin-marketshare + data: + icon: coin +- id: oracleDiversity + title: Oracle Diversity + # link: /metrics/oracle-diversity + data: + icon: megaphone +- id: decentralizedOracleMarketshare + title: Decentralized Oracle Marketshare + # link: /metrics/decentralized-oracle-marketshare + data: + icon: megaphone +- id: l2NetworkDiversity + title: L2 Network Diversity + # link: /metrics/l2-network-diversity + data: + icon: stack +- id: l2NativeAssetMarketshare + title: L2 Native Asset Marketshare + # link: /metrics/l2-native-asset-marketshare + data: + icon: stack +- id: relayDiversity + title: Relay Diversity + # link: /metrics/relay-diversity + data: + icon: block_grid +- id: relayCensoringMarketshare + title: Relay Censoring Marketshare + # link: /metrics/relay-censoring-marketshare + data: + icon: block_grid +- id: energyUsage + title: Energy Usage + # link: /metrics/energy-usage + data: + icon: plugin +- id: validatorAffordability + title: Validator Affordability + # link: /metrics/validator-affordability + data: + icon: wallet + +# this should be a category with each layer +# - id: l2ClientDiversity +# title: L2 Client Diversity +# # link: /metrics/l2-client-diversity +# data: +# icon: stack +# not sure if keep this +# - id: securityRating +# title: Security Rating +# # link: /metrics/security-rating +# data: +# icon: shield + + +# search for more icons +# "lay" +# "app" +# "diamond" +# "re" + + + + diff --git a/_data/icons.yml b/_data/icons.yml new file mode 100644 index 0000000..f3c3104 --- /dev/null +++ b/_data/icons.yml @@ -0,0 +1,82 @@ +discord: '' +twitter: '' +youtube: '' +reddit: '' +telegram: '' +twitch: '' +mastodon: '' +github: '' +etherscan: '' +mail: '' +mail2: '' +calendar: '' +new_tab: '' +link: '' +ethereum: '' +wallet: '' +hardware_wallet: '' +globe: '' +cpu: '' +laptop: '' +desktop: '' +nuc: '' +pc: '' +motherboard: '' +hdd: '' +monitor: '' +router: '' +terminal: '' +window: '' +stack: '' +plugin: '' +people: '' +code: '' +coin: '' +shield: '' +lightning: '' +diagram: '' +megaphone: '' +block_grid: '' +person_lock: '' +graph_up: '' +window_sidebar: '' +window_split: '' +shopping_cart: '' +download: '' +exclamation_triangle: '' +exclamation_triangle_fill: '' +check_circle: '' +check_circle_fill: '' +info_circle: '' +info_circle_fill: '' +exclamation_octagon: '' +exclamation_octagon_fill: '' +sun: '' +docs: '' +tool: '' +chat: '' +donate: '' +etherscan: '' +gitcoin: '' +checkmark: '' +left_arrow: '' +right_arrow: '' +down_up_arrow: '' +left_right_arrow: '' +arrow-clockwise: '' +circle: '' +star: '' +star_fill: '' +copy: '' +sort: '' +cloud: '' +saas_blox: '' +saas_abyss: '' +saas_ethpool: '' +saas_kiln: '' +saas_allnodes: '' +pool_rocketpool: '' +pool_stakewise: '' +pool_lido: '' +cex_coinbase: '' +cex_binance: '' diff --git a/_data/metrics/consensus-client-diversity-validators/blockprint.json b/_data/metrics/consensus-client-diversity-validators/blockprint.json new file mode 100644 index 0000000..664ea61 --- /dev/null +++ b/_data/metrics/consensus-client-diversity-validators/blockprint.json @@ -0,0 +1 @@ +[{"date":"2023-08-23","timestamp":1692772120,"data":{"distribution":[{"name":"prysm","value":0.462712,"accuracy":0.92825},{"name":"lighthouse","value":0.332081,"accuracy":0.985609},{"name":"teku","value":0.149273,"accuracy":0.672749},{"name":"nimbus","value":0.04467,"accuracy":0.92174},{"name":"lodestar","value":0.011265,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"accuracy":[{"name":"grandine","value":"0"},{"name":"lighthouse","value":0.985609},{"name":"lodestar","value":"0"},{"name":"nimbus","value":0.92174},{"name":"prysm","value":0.92825},{"name":"teku","value":0.672749}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-24","timestamp":1692840408,"data":{"distribution":[{"name":"prysm","value":0.4633251590007377,"accuracy":0.928146},{"name":"lighthouse","value":0.332137089538848,"accuracy":0.98574},{"name":"teku","value":0.14985944135414797,"accuracy":0.670723},{"name":"nimbus","value":0.04357318021412764,"accuracy":0.921164},{"name":"lodestar","value":0.011105129892138684,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-25","timestamp":1692926929,"data":{"distribution":[{"name":"prysm","value":0.4635925959151939,"accuracy":0.931392},{"name":"lighthouse","value":0.3324761021899265,"accuracy":0.985606},{"name":"teku","value":0.14976625499636176,"accuracy":0.670745},{"name":"nimbus","value":0.042781814738394985,"accuracy":0.921489},{"name":"lodestar","value":0.011383232160122804,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-26","timestamp":1693013098,"data":{"distribution":[{"name":"lighthouse","value":0.33121012759170654,"accuracy":0.985113},{"name":"teku","value":0.15039872408293462,"accuracy":0.671471},{"name":"nimbus","value":0.04242424242424243,"accuracy":0.922246},{"name":"lodestar","value":0.011423444976076554,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-27","timestamp":1693099880,"data":{"distribution":[{"name":"prysm","value":0.464328079986443,"accuracy":0.935549},{"name":"lighthouse","value":0.33172842090572885,"accuracy":0.984943},{"name":"teku","value":0.15023375898402067,"accuracy":0.664566},{"name":"nimbus","value":0.04227598512714694,"accuracy":0.922109},{"name":"lodestar","value":0.011433754996660586,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-28","timestamp":1693186162,"data":{"distribution":[{"name":"prysm","value":0.4647505193098478,"accuracy":0.935549},{"name":"lighthouse","value":0.33183899275085843,"accuracy":0.984943},{"name":"teku","value":0.15021832209928357,"accuracy":0.664566},{"name":"nimbus","value":0.04183093814913731,"accuracy":0.922109},{"name":"lodestar","value":0.011361227690872864,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-29","timestamp":1693272556,"data":{"distribution":[{"name":"prysm","value":0.46435587287975033,"accuracy":0.935549},{"name":"lighthouse","value":0.33204032587776594,"accuracy":0.984943},{"name":"teku","value":0.1502747225352253,"accuracy":0.664566},{"name":"nimbus","value":0.04203105212249334,"accuracy":0.922109},{"name":"lodestar","value":0.011298026584765114,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-30","timestamp":1693358917,"data":{"distribution":[{"name":"prysm","value":0.4652452280750743,"accuracy":0.93743},{"name":"lighthouse","value":0.33209007319943357,"accuracy":0.984991},{"name":"teku","value":0.14949039631409936,"accuracy":0.652251},{"name":"nimbus","value":0.041935097831940486,"accuracy":0.922295},{"name":"lodestar","value":0.011239204579452301,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-08-31","timestamp":1693445362,"data":{"distribution":[{"name":"prysm","value":0.46511140811074986,"accuracy":0.939155},{"name":"lighthouse","value":0.3318206299495322,"accuracy":0.985116},{"name":"teku","value":0.14951826215315872,"accuracy":0.635991},{"name":"nimbus","value":0.0422892022900002,"accuracy":0.924713},{"name":"lodestar","value":0.011260497496559016,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-01","timestamp":1693532046,"data":{"distribution":[{"name":"prysm","value":0.464951127069619,"accuracy":0.940111},{"name":"lighthouse","value":0.3320267305006982,"accuracy":0.974355},{"name":"teku","value":0.1492519449431478,"accuracy":0.619278},{"name":"nimbus","value":0.04253939756632755,"accuracy":0.926459},{"name":"lodestar","value":0.01123079992020746,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-02","timestamp":1693617930,"data":{"distribution":[{"name":"prysm","value":0.4638016495955798,"accuracy":0.939424},{"name":"lighthouse","value":0.3324623255906729,"accuracy":0.955756},{"name":"teku","value":0.14893235062383436,"accuracy":0.605237},{"name":"nimbus","value":0.043553711590054556,"accuracy":0.927532},{"name":"lodestar","value":0.011249962599858379,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-03","timestamp":1693704707,"data":{"distribution":[{"name":"prysm","value":0.46335478078707165,"accuracy":0.937891},{"name":"lighthouse","value":0.3320065838695197,"accuracy":0.937477},{"name":"teku","value":0.14851613546810316,"accuracy":0.608754},{"name":"nimbus","value":0.04487006833258517,"accuracy":0.928676},{"name":"lodestar","value":0.011252431542720336,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-04","timestamp":1693790994,"data":{"distribution":[{"name":"prysm","value":0.46315673971810195,"accuracy":0.937515},{"name":"lighthouse","value":0.33143472752845415,"accuracy":0.919473},{"name":"teku","value":0.14819100439904637,"accuracy":0.606164},{"name":"nimbus","value":0.045935620305439455,"accuracy":0.930253},{"name":"lodestar","value":0.011281908048958095,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-05","timestamp":1693877267,"data":{"distribution":[{"name":"prysm","value":0.46310539179011423,"accuracy":0.937176},{"name":"lighthouse","value":0.33051024988777494,"accuracy":0.899844},{"name":"teku","value":0.1476781884383261,"accuracy":0.602032},{"name":"nimbus","value":0.04735398274228141,"accuracy":0.933676},{"name":"lodestar","value":0.011352187141503317,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-06","timestamp":1693963746,"data":{"distribution":[{"name":"prysm","value":0.46233030433004496,"accuracy":0.936745},{"name":"lighthouse","value":0.33021455717035897,"accuracy":0.873523},{"name":"teku","value":0.14793572262176694,"accuracy":0.599765},{"name":"nimbus","value":0.04821800843865021,"accuracy":0.936974},{"name":"lodestar","value":0.011301407439178877,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-07","timestamp":1694050158,"data":{"distribution":[{"name":"prysm","value":0.46173876606273445,"accuracy":0.936145},{"name":"lighthouse","value":0.32977492218054116,"accuracy":0.863825},{"name":"teku","value":0.14773724958097215,"accuracy":0.595516},{"name":"nimbus","value":0.049355495251017636,"accuracy":0.940407},{"name":"lodestar","value":0.011393566924734616,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-08","timestamp":1694136579,"data":{"distribution":[{"name":"prysm","value":0.46047091689115033,"accuracy":0.935921},{"name":"lighthouse","value":0.33020053876085004,"accuracy":0.851957},{"name":"teku","value":0.14723136785393595,"accuracy":0.591762},{"name":"nimbus","value":0.05080315274867804,"accuracy":0.942986},{"name":"lodestar","value":0.011294023745385613,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-09","timestamp":1694222782,"data":{"distribution":[{"name":"prysm","value":0.459331098339719,"accuracy":0.936008},{"name":"lighthouse","value":0.3309087643678161,"accuracy":0.837679},{"name":"teku","value":0.14690094189016603,"accuracy":0.591138},{"name":"nimbus","value":0.05164431673052363,"accuracy":0.945382},{"name":"lodestar","value":0.011214878671775223,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}},{"date":"2023-09-10","timestamp":1697305000,"data":{"distribution":[{"name":"prysm","value":0.45873948657574154,"accuracy":0.935828},{"name":"lighthouse","value":0.33044328487763025,"accuracy":0.823973},{"name":"teku","value":0.1476618543535304,"accuracy":0.589206},{"name":"nimbus","value":0.051941054984086445,"accuracy":0.948214},{"name":"lodestar","value":0.011214319209011364,"accuracy":"0"},{"name":"other","value":0.0,"accuracy":"no data"},{"name":"grandine","value":0.0,"accuracy":"0"}],"other":{"data_source":"blockprint","has_majority":false,"has_supermajority":false,"danger_client":"","top_client":"prysm"}}}] \ No newline at end of file diff --git a/_data/metrics/consensus-client-diversity-validators/client-info.yml b/_data/metrics/consensus-client-diversity-validators/client-info.yml new file mode 100644 index 0000000..d84bfbb --- /dev/null +++ b/_data/metrics/consensus-client-diversity-validators/client-info.yml @@ -0,0 +1,86 @@ +## - name: = client name +## link: = link to website +## github: = link to github +## docs: = link to documentation +## chat: = link to community chat +## status: = (select one) Alpha, Beta, Stable +## support: = Linux, Win, macOS, ARM +## lang = programming language it's written in +## donate: = link to gitcoin or etherscan address +## opensource: = true/false + +# Template to copy for new entries +# - name: +# link: +# github: +# docs: +# chat: +# status: +# support: +# donate: +# opensource: + + +# Entries are shown in the order listed +- name: Grandine + link: https://sifrai.com/ + github: https://github.com/sifraitech/grandine + docs: https://github.com/sifraitech/grandine + chat: https://discord.gg/H9XCdUSyZd + status: Beta + support: Linux, Win, macOS + lang: + donate: + opensource: false +- name: Lighthouse + link: https://lighthouse.sigmaprime.io/ + github: https://github.com/sigp/lighthouse/ + docs: https://lighthouse-book.sigmaprime.io/ + chat: https://discord.gg/cyAszAh + status: Stable + support: Linux, Win, macOS, ARM + lang: Rust + donate: https://protocol-guild.readthedocs.io/en/latest/index.html + opensource: true +- name: Lodestar + link: https://lodestar.chainsafe.io/ + github: https://github.com/ChainSafe/lodestar + docs: https://hackmd.io/@philknows/rk5cDvKmK + # docs: https://chainsafe.github.io/lodestar/ + chat: https://discord.gg/yjyvFRP + status: Stable + support: Linux, Win, macOS # windows theoretically works but not tested yet https://github.com/ChainSafe/lodestar/issues/3519 + lang: TypeScript + donate: https://protocol-guild.readthedocs.io/en/latest/index.html + opensource: true +- name: Nimbus + link: https://nimbus.team/ + github: https://github.com/status-im/nimbus-eth2 + docs: https://nimbus.team/docs/ + chat: https://discord.gg/qnjVyhatUa + status: Stable + support: Linux, Win, macOS, ARM + lang: Nim + donate: + opensource: true +- name: Prysm + link: https://prysmaticlabs.com/ + github: https://github.com/prysmaticlabs/prysm + docs: https://docs.prylabs.network/docs/getting-started/ + chat: https://discord.gg/YMVYzv6 + status: Stable + support: Linux, Win, macOS, ARM + lang: Golang + donate: funded # Supported by Offchain Labs + opensource: true +- name: Teku + link: https://consensys.net/knowledge-base/ethereum-2/teku/ + github: https://github.com/ConsenSys/teku + docs: https://docs.teku.consensys.net/en/latest/ + chat: https://discord.gg/9mCVSY6 + status: Stable + support: Linux, Win, macOS + lang: Java + donate: funded # funded by ConsenSys + opensource: true + diff --git a/_data/metrics/consensus-client-diversity-validators/consensus-migration-guides.yml b/_data/metrics/consensus-client-diversity-validators/consensus-migration-guides.yml new file mode 100644 index 0000000..498f921 --- /dev/null +++ b/_data/metrics/consensus-client-diversity-validators/consensus-migration-guides.yml @@ -0,0 +1,295 @@ +## - link: = required +## author: = (optional) team/name/handle/etc, example: "Yorick" +## type: = (optional) "doc", "video", or "tool" (use tool for automated migration) +## note: = (optional) short note, example: "w/ ethdocker" + +# Example: +# - link: https://eth-docker.net/Support/SwitchClient +# author: Yorick +# type: doc +# note: w/ ethdocker +# User sees: +# (doc icon) Prysm to Nimbus migration guide (w/ ethdocker), by Yorick + +# Template to copy for new entries +# - link: +# author: +# type: +# note: + + +# Entries are shown in the order listed +blank_to_lighthouse: + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-2-0-ubuntu-lighthouse-41de20513b12 + author: Somer Esat + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet + author: CoinCashew + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-testnet + author: CoinCashew + type: doc + note: w/ Ubuntu - Testnet +blank_to_lodestar: + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-ubuntu-goerli-lodestar-f3c8f77e7097 + author: Somer Esat + type: doc + note: w/ Ubuntu - Testnet + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-ubuntu-lodestar-193a2553a161 + author: Somer Esat + type: doc + note: w/ Ubuntu + - link: https://youtu.be/0xo85F-_fag + author: Phil Ngo + type: video + note: w/ Ubuntu/Docker + - link: https://hackmd.io/@philknows/rk5cDvKmK + author: Phil Ngo + type: doc + note: w/ Ubuntu/Docker + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet + author: CoinCashew + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-testnet + author: CoinCashew + type: doc + note: w/ Ubuntu - Testnet +blank_to_nimbus: + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-2-0-ubuntu-nimbus-e86bdee8c550 + author: Somer Esat + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet + author: CoinCashew + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-testnet + author: CoinCashew + type: doc + note: w/ Ubuntu - Testnet +blank_to_prysm: + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-2-0-ubuntu-prysm-56f681646f74 + author: Somer Esat + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet + author: CoinCashew + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-testnet + author: CoinCashew + type: doc + note: w/ Ubuntu - Testnet +blank_to_teku: + - link: https://someresat.medium.com/guide-to-staking-on-ethereum-2-0-ubuntu-teku-e4247e7c75a1 + author: Somer Esat + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet + author: CoinCashew + type: doc + note: w/ Ubuntu + - link: https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-testnet + author: CoinCashew + type: doc + note: w/ Ubuntu - Testnet +blank_to_grandine: +lighthouse_to_lodestar: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +lighthouse_to_nimbus: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://nimbus.guide/migration.html + author: Nimbus + type: doc + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +lighthouse_to_prysm: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +lighthouse_to_teku: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +lighthouse_to_grandine: +lodestar_to_lighthouse: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +lodestar_to_nimbus: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +lodestar_to_prysm: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +lodestar_to_teku: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +lodestar_to_grandine: +nimbus_to_lighthouse: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +nimbus_to_lodestar: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +nimbus_to_prysm: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +nimbus_to_teku: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +nimbus_to_grandine: +prysm_to_lighthouse: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://lighthouse-blog.sigmaprime.io/switch-to-lighthouse.html + author: Michael Sproul + type: doc + - link: https://github.com/cprest0n/eth-guides/blob/main/migrate-prysm-to-lighthouse.md + author: cprest0n + type: doc + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +prysm_to_lodestar: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +prysm_to_nimbus: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://nimbus.guide/migration.html + author: Nimbus + type: doc + - link: https://www.reddit.com/r/ethstaker/comments/pu30fa/short_guide_to_migrate_from_prysm_to_teku_or/ + author: u/Breakbeatjuggernaut + type: doc + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +prysm_to_teku: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://www.coincashew.com/coins/overview-eth/guide-or-operation-client-diversity-migrate-prysm-to-teku + author: CoinCashew + type: doc + - link: https://www.reddit.com/r/ethstaker/comments/pu30fa/short_guide_to_migrate_from_prysm_to_teku_or/ + author: u/Breakbeatjuggernaut + type: doc + - link: https://www.reddit.com/r/ethstaker/comments/pyfg0b/guide_migrate_from_prysm_to_teku_with_a_clean/ + author: u/deecoydev + type: doc + note: w/ clean Ubuntu install + - link: https://www.reddit.com/r/ethstaker/comments/pzl1mn/migrating_from_prysm_to_teku_windows_edition/ + author: u/flossraptor + type: doc + note: Windows Edition + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +prysm_to_grandine: +teku_to_lighthouse: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +teku_to_lodestar: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker +teku_to_nimbus: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://nimbus.guide/migration.html + author: Nimbus + type: doc + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +teku_to_prysm: + - link: https://eth-docker.net/Support/SwitchClient + author: Yorick + type: doc + note: w/ ethdocker + - link: https://twitter.com/jcrtp_eth/status/1501216542511575051 + author: jcrtp + type: video + note: w/ Rocket Pool +teku_to_grandine: +grandine_to_lighthouse: +grandine_to_lodestar: +grandine_to_nimbus: +grandine_to_prysm: +grandine_to_teku: + + + + + + diff --git a/_data/metrics/consensus-client-diversity-validators/content.yml b/_data/metrics/consensus-client-diversity-validators/content.yml new file mode 100644 index 0000000..6102b0b --- /dev/null +++ b/_data/metrics/consensus-client-diversity-validators/content.yml @@ -0,0 +1,110 @@ +toc: + - Data + - Risks + - Take Action + - Client Info + - Resources +description: This metric covers the marketshare and diversity of consensus layer clients across the network’s validator set. +health_methodology: | + For this metric it is calculated by taking the blah blah blah. +data: + data_limits: + success_start: 0 + warning_start: 33 + danger_start: 50 + critical_start: 66 # used for health calculation + max_value: 100 + data_folder: metrics/consensus-client-diversity-validators + datasets: + - data_id: blockprint + data_file_name: blockprint + data_source: blockprint + data_attribute: | + Data provided by [Sigma Prime's Blockprint](https://blockprint.sigp.io/). + data_methodology: | + Blockprint analyzes each client’s block proposal style as described in [this Twitter thread](https://twitter.com/sproulM_/status/1440512518242197516) ([Nitter](https://nitter.snopyta.org/sproulM_/status/1440512518242197516)). + historical_chart: true + data_obj_key: "distribution" + - data_id: blockprint2 + data_file_name: blockprint + data_source: blockprint2 + data_attribute: | + Data provided by [Sigma Prime's Blockprint2](https://blockprint.sigp.io/) + data_methodology: | + Blockprint2 analyzes each client’s block proposal style as described in [this Twitter thread](https://twitter.com/sproulM_/status/1440512518242197516) ([Nitter](https://nitter.snopyta.org/sproulM_/status/1440512518242197516)). + historical_chart: false + manually_updated: true +risks: | + Validator client diversity is critical for network resiliency. If a single client is used by 2/3rds (66%) of validators, there's a very real risk this can result in disrupting the chain and monetary loss \[[1](https://www.slashed.info/), [2](https://nitter.net/_crypto_crack/status/1504459918539120643)] for node operators. + + It takes 2/3rds of validators to reach finality. If a client with 66%+ of marketshare has a bug and forks to its own chain, it'll be capable of finalizing. Once the fork finalizes, the **validators cannot return to the real chain without being slashed**. If 66% of the chain gets slashed simultaneously, the penalty is the whole 32 ETH. + + So why is >50% marketshare still dangerous? If a minority client forks, the 50%+ majority client can obtain a 66%+ majority. With no client having a marketshare over 33%, these scenarios are avoided. That's why **<33% marketshare is the goal for all clients**. +take_action: + - title: Node Operators - Manual Client Migration Guides + content: partials/metrics/consensus-client-diversity-validators/take-action/client-migration-manual.html + - title: Liquid Stakers - Use LST With Diverse Clients + content: | + [Use a staking pool](https://www.rated.network/?network=mainnet&view=pool&timeWindow=1d&page=1&poolType=all) that has better consensus client distribution, but a network penetration less than 22%. +further_reading: + - title: "Client Diversity On Ethereum’s Consensus Layer" + link: "https://mirror.xyz/jmcook.eth/S7ONEka_0RgtKTZ3-dakPmAHQNPvuj15nh0YGKPFriA" + - title: "Prysm: Statement On Client Diversity" + link: "https://medium.com/prysmatic-labs/prysmatic-labs-statement-on-client-diversity-c0e3c2f05671" + - title: "Nimbus: The Importance Of Client Diversity" + link: "https://our.status.im/the-importance-of-client-diversity/" + - title: "Lighthouse: Why You Should Switch To Lighthouse" + link: "https://lighthouse.sigmaprime.io/switch-to-lighthouse.html" + - title: "Chainsafe: Client Diversity In Decentralized Networks" + link: "https://medium.com/chainsafe-systems/on-client-diversity-in-decentralized-networks-848aeedfb49d" + - title: "The Financial Incentive To Run A Minority Client" + link: "https://www.reddit.com/r/ethstaker/comments/ptm04i/the_financial_incentive_to_run_a_minority_client/" + - title: "What Happens If Beacon Chain Consensus Fails?" + link: "https://www.symphonious.net/2021/09/23/what-happens-if-beacon-chain-consensus-fails/" + - title: "Ben Edgington On Diversity, Scenarios, And Penalties" + link: "https://upgrading-ethereum.info/altair/part2/incentives/diversity" +resources: + - category: Software + links: + - title: "Instant Validator" + link: "https://github.com/accidental-green/validator-install" + - title: "Eth-Wizard" + link: "https://github.com/stake-house/eth-wizard" + - title: "Eth-Docker" + link: "https://ethdocker.com/" + - title: "Stereum" + link: "https://stereum.net/" + - title: "Vouch" + link: "https://github.com/attestantio/vouch" + - title: "Kotal" + link: "https://www.kotal.co/" + - category: Data + links: + - title: "Staking Pool Client Diversity" + link: "https://www.rated.network/" + - title: "Miga Labs Dashboard" + link: "https://monitoreth.io/" + - title: "Chainsafe Nodewatch" + link: "https://www.nodewatch.io/" + - title: "Proposer Diversity Data" + link: "https://github.com/sigp/blockprint/blob/main/docs/api.md" + - title: "Rated.Network Validator Ratings" + link: "https://www.rated.network/" + - title: "Financial Risk Per Consensus Client" + link: "https://www.slashed.info/" + - category: Tools + links: + - title: "Ethereum Staking Resources" + link: "https://ethstaker.cc/resources" + - title: "Keymanager APIs" + link: "https://github.com/ethereum/keymanager-APIs" + - category: Research + links: + - title: "Client Fingerprinting" + link: "https://twitter.com/sproulM_/status/1440512518242197516" + - title: "EIP-3076: Slashing Protection Interchange Format" + link: "https://eips.ethereum.org/EIPS/eip-3076" + + + + diff --git a/_data/nav-menu.yml b/_data/nav-menu.yml new file mode 100644 index 0000000..bfde1e5 --- /dev/null +++ b/_data/nav-menu.yml @@ -0,0 +1,119 @@ +## category = category name +## label = (optional) category label +## disabled = (optional) true/false +## items = list of items in the category +## subcategory = subcategory name +## subitems = list of items in the subcategory +## link = subcategory item permalink +## title = subcategory item title +## new_tab = (optional) true/false; open in new tab or not +## label = (optional) subcategory item label +## disabled = (optional) true/false +## link = category item link +## title = category item title +## new_tab = (optional) true/false; open in new tab or not +## label = (optional) category item label +## disabled = (optional) true/false + +# Template to copy for new entries +# - category: +# items: +# - subcategory: +# sub_items: +# - link: +# title: +# - link: +# title: + + +# Entries are shown in the order listed +- category: Dashboards + items: + - id: dashboardOverview + - id: dashboardWatchlist + label: new +- category: Metrics + items: + - subcategory: Consensus Client Diversity + icon: window_sidebar + sub_items: + - link: /metrics/consensus-client-diversity-nodes + title: Node Client Diversity + label: Soon™ + disabled: true + - id: consensus-client-diversity-validators + - link: /metrics/consensus-client-count + title: Client Count + label: Soon™ + disabled: true + - link: /metrics/consensus-client-count + title: Client Languages + label: Soon™ + disabled: true + - subcategory: Execution Client Diversity + icon: window_split + label: Soon™ + disabled: true + sub_items: + - link: /metrics/execution-client-diversity-nodes + title: Node Client Diversity + - link: /metrics/execution-client-diversity-validators + title: Validator Client Diversity + - link: /metrics/execution-client-count + title: Client Count + - link: /metrics/execution-client-count + title: Client Languages + - subcategory: Staking Diversity + icon: people + label: Soon™ + disabled: true + sub_items: + - link: /metrics/entity-staking-diversity + title: Entities + label: Soon™ + disabled: true + - link: /metrics/lst-staking-diversity + title: LSTs / Pools + - subcategory: Geographic Diversity + icon: globe + label: Soon™ + disabled: true + sub_items: + - link: /metrics/geographic-diversity-nodes + title: Nodes + label: Soon™ + disabled: true + - link: /metrics/geographic-diversity-validators + title: Validators + label: Soon™ + disabled: true + - subcategory: Block Building + icon: block_grid + label: Soon™ + disabled: true + sub_items: + - link: /metrics/relay-diversity + title: Relay Diversity + label: Soon™ + disabled: true + - link: /metrics/relay-censoring-marketshare + title: Relay Censoring Marketshare + label: Soon™ + disabled: true + - link: /metrics/node-count + title: Node Count + icon: plugin + label: Soon™ + disabled: true +- category: Tools + items: + - id: apiV1Documentation + label: Alpha +- category: General + items: + - id: about + - id: newsletter + - id: twitter + - id: github + - id: donate + diff --git a/_data/sponsors.yml b/_data/sponsors.yml new file mode 100644 index 0000000..31b269f --- /dev/null +++ b/_data/sponsors.yml @@ -0,0 +1,28 @@ +## img = sponsor img link +## title = (optional) sponsor name +## link = (optional) sponsor link +## tier = sponsor tier (1-3); lower number = higher donation + +# Template to copy for new entries +# - img: "/assets/img/sponsors/" +# title: +# link: + + +# Entries are shown in the order listed +- img: "/assets/img/sponsors/ef.svg" + title: Ethereum Foundation + link: "https://ethereum.foundation/" + tier: 1 +- img: "/assets/img/sponsors/ethstaker.svg" + title: EthStaker + link: "https://ethstaker.cc" + tier: 1 +# - img: "/assets/img/sponsors/diva.svg" +# title: Diva +# link: "https://diva.community/" +# tier: 3 +- img: "/assets/img/sponsors/staking-foundation.svg" + title: Staking Foundation + link: "https://staking.foundation/" + tier: 3 diff --git a/_includes/components/calculate_health.html b/_includes/components/calculate_health.html new file mode 100644 index 0000000..3594b16 --- /dev/null +++ b/_includes/components/calculate_health.html @@ -0,0 +1,91 @@ +{%- comment -%} + +{%- endcomment -%} + + +{%- assign data_folder = include.id | append: "/" | split: "/" | first -%} +{%- assign data_path = site.data.metrics -%} +{%- for item in data_folder -%} + {%- assign data_path = data_path[item] -%} +{%- endfor -%} +{%- assign content_file = data_path.content -%} + +{%- assign success_start = content_file.data.data_limits.success_start -%} +{%- assign warning_start = content_file.data.data_limits.warning_start -%} +{%- assign danger_start = content_file.data.data_limits.danger_start -%} +{%- assign critical_start = content_file.data.data_limits.critical_start -%} +{%- assign max_value = content_file.data.data_limits.max_value -%} + +{%- assign health_success_start = 80 -%} +{%- assign health_warning_start = 50 -%} +{%- assign health_danger_start = 5 -%} +{%- assign health_success_range = 100 | minus: health_success_start -%} +{%- assign health_warning_range = health_success_start | minus: health_warning_start -%} +{%- assign health_danger_range = health_warning_start | minus: health_danger_start -%} +{%- assign health_critical_range = health_danger_start -%} + +{%- assign data_set = content_file.data.datasets | first -%} +{%- assign data_file_name = data_set.data_file_name -%} +{%- assign data_obj_key = data_set.data_obj_key -%} +{%- assign all_data = data_path[data_file_name] -%} +{%- assign spot_data = all_data | last -%} +{%- assign spot_data_array = spot_data.data[data_obj_key] -%} +{%- assign spot_data_obj = spot_data_array | first -%} + +{%- assign data_value = spot_data_obj.value | times: 100 -%} +{%- assign health_value = nil -%} + +{%- if data_value >= critical_start -%} + {%- assign health_value = 1 -%} + {%- assign scope = "critical" -%} + {%- assign delta = data_value | minus: critical_start -%} + {%- assign range = max_value | minus: critical_start -%} + {%- assign range_percentage = delta | plus: 0.00 | divided_by: range -%} + {%- assign range_percentage = 1 | minus: range_percentage -%} + {%- assign health_value = health_critical_range | times: range_percentage -%} + {%- if health_value < 1 -%} + {%- assign health_value = 1 -%} + {%- endif -%} +{%- elsif data_value >= danger_start -%} + {%- assign scope = "danger" -%} + {%- assign delta = data_value | minus: danger_start -%} + {%- assign range = critical_start | minus: danger_start -%} + {%- assign range_percentage = delta | plus: 0.00 | divided_by: range -%} + {%- assign range_percentage = 1 | minus: range_percentage -%} + {%- assign health_value = health_danger_range | times: range_percentage | plus: health_danger_start -%} +{%- elsif data_value >= warning_start -%} + {%- assign scope = "warning" -%} + {%- assign delta = data_value | minus: warning_start -%} + {%- assign range = danger_start | minus: warning_start -%} + {%- assign range_percentage = delta | plus: 0.00 | divided_by: range -%} + {%- assign range_percentage = 1 | minus: range_percentage -%} + {%- assign health_value = health_warning_range | times: range_percentage | plus: health_warning_start -%} +{%- else -%} + {%- assign scope = "success" -%} + {%- assign delta = data_value | minus: success_start -%} + {%- assign range = warning_start | minus: success_start -%} + {%- assign range_percentage = delta | plus: 0.00 | divided_by: range -%} + {%- assign range_percentage = 1 | minus: range_percentage -%} + {%- assign health_value = health_success_range | times: range_percentage | plus: health_success_start -%} +{%- endif -%} + +{%- comment -%} + +{%- endcomment -%} + +{{health_value | round: 2}} \ No newline at end of file diff --git a/_includes/components/card-alert.html b/_includes/components/card-alert.html new file mode 100644 index 0000000..560ea3b --- /dev/null +++ b/_includes/components/card-alert.html @@ -0,0 +1,99 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign type = "alert-info" -%} +{%- assign icon = "" -%} +{%- if include.type == "primary" -%} + {%- assign type = "alert-primary" -%} + {%- assign icon = site.data.icons.info_circle_fill -%} +{%- endif -%} +{%- if include.type == "secondary" -%} + {%- assign type = "alert-secondary" -%} +{%- endif -%} +{%- if include.type == "success" -%} + {%- assign type = "alert-success" -%} + {%- assign icon = site.data.icons.check_circle_fill -%} +{%- endif -%} +{%- if include.type == "danger" -%} + {%- assign type = "alert-danger" -%} + {%- assign icon = site.data.icons.exclamation_triangle_fill -%} +{%- endif -%} +{%- if include.type == "warning" -%} + {%- assign type = "alert-warning" -%} + {%- assign icon = site.data.icons.exclamation_triangle_fill -%} +{%- endif -%} +{%- if include.type == "info" -%} + {%- assign type = "alert-info" -%} + {%- assign icon = site.data.icons.info_circle_fill -%} +{%- endif -%} +{%- if include.type == "light" -%} + {%- assign type = "alert-light" -%} +{%- endif -%} +{%- if include.type == "dark" -%} + {%- assign type = "alert-dark" -%} +{%- endif -%} + +{%- if include.icon == false -%} + {%- assign icon = "" -%} +{%- endif -%} + + + + +{%- if include.body -%} + +{%- else -%} + +{%- endif -%} diff --git a/_includes/components/card-list.html b/_includes/components/card-list.html new file mode 100644 index 0000000..25f5e01 --- /dev/null +++ b/_includes/components/card-list.html @@ -0,0 +1,32 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign id = "" | append: include.title -%} +
+
+ {%- if include.title -%} +

{{include.title}}

+ {%- endif -%} +
+ {%- if include.lists -%} + {%- for list in include.lists -%} +
+ {{list | markdownify}} +
+ {%- endfor -%} + {%- endif -%} +
+ {%- if include.button_link and include.button_text -%} + {{include.button_text}} + {%- endif -%} +
+
diff --git a/_includes/components/card-msg.html b/_includes/components/card-msg.html new file mode 100644 index 0000000..e9dfe87 --- /dev/null +++ b/_includes/components/card-msg.html @@ -0,0 +1,26 @@ +{%- comment -%} + +{%- endcomment -%} + + + + +
+
+ {%- if include.body -%} + {{include.body | markdownify}} + {%- endif -%} +
+
diff --git a/_includes/components/card-table.html b/_includes/components/card-table.html new file mode 100644 index 0000000..f8e5289 --- /dev/null +++ b/_includes/components/card-table.html @@ -0,0 +1,45 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign id = "" | append: include.title -%} +
+
+ {%- if include.title -%} +

+ {{include.title}} + {%- if include.tooltip -%} + + + + {%- endif -%} +

+ {%- endif -%} + {%- if include.table -%} +
+ {{include.table | markdownify}} +
+ {%- endif -%} + {%- if include.body -%} + {{include.body | markdownify}} + {%- endif -%} + {%- if include.caption -%} +
+ {{include.caption | markdownify}} +
+ {%- endif -%} + {%- if include.button_link and include.button_text -%} + {{include.button_text}} + {%- endif -%} +
+
diff --git a/_includes/components/card-text.html b/_includes/components/card-text.html new file mode 100644 index 0000000..65a4730 --- /dev/null +++ b/_includes/components/card-text.html @@ -0,0 +1,33 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign id = "" | append: include.title -%} +
+
+ {%- if include.title -%} +

+ {{include.title}} + {%- if include.tooltip -%} + + + + {%- endif -%} +

+ {%- endif -%} + {%- if include.body -%} + {{include.body | markdownify}} + {%- endif -%} + {%- if include.button_link and include.button_text -%} + {{include.button_text}} + {%- endif -%} +
+
diff --git a/_includes/components/card-toc.html b/_includes/components/card-toc.html new file mode 100644 index 0000000..503fdbd --- /dev/null +++ b/_includes/components/card-toc.html @@ -0,0 +1,47 @@ +{%- comment -%} + +{%- endcomment -%} + + + + +
+

+ {%- for item in include.headers -%} + {%- assign header = "" | append: item -%} + {{item}} + {%- endfor -%} +

+
diff --git a/_includes/components/data-line-chart.html b/_includes/components/data-line-chart.html new file mode 100644 index 0000000..7bd96c9 --- /dev/null +++ b/_includes/components/data-line-chart.html @@ -0,0 +1,175 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign chart_id = include.chart_id -%} +{%- assign data = include.data -%} +{%- assign data_obj_key = include.data_obj_key -%} + +{%- assign legend_color = 'rgba(225, 226, 227, 0.9)' -%} +{%- assign ticks_color = 'rgba(225, 226, 227, 0.9)' -%} +{%- assign grid_color = 'rgba(43, 43, 43, 0.435)' -%} + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + {%- include components/watermark.html -%} +
+ {%- if include.data_attribute -%} +

+ {{include.data_attribute | markdownify | remove: "

" | remove: "

"}} +

+ {%- endif -%} +
+
+ + + + + + diff --git a/_includes/components/data-progress-bars.html b/_includes/components/data-progress-bars.html new file mode 100644 index 0000000..661d683 --- /dev/null +++ b/_includes/components/data-progress-bars.html @@ -0,0 +1,117 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign all_data = include.data -%} +{%- assign spot_data = all_data | last -%} +{%- assign data = spot_data.data.distribution -%} +{%- assign success_start = include.content.data.data_limits.success_start -%} +{%- assign warning_start = include.content.data.data_limits.warning_start -%} +{%- assign danger_start = include.content.data.data_limits.danger_start -%} +{%- assign critical_start = include.content.data.data_limits.critical_start -%} +{%- assign max_value = include.content.data.data_limits.max_value -%} + +{%- assign max_value_int = max_value | times: 1.00 -%} +{%- assign success_width = warning_start | divided_by: max_value_int | times: 100 -%} +{%- assign warning_width = danger_start | minus: warning_start | divided_by: max_value_int | times: 100 -%} +{%- assign danger_width = max_value | minus: danger_start | divided_by: max_value_int | times: 100 -%} + +{%- assign percent = "" -%} +{%- if max_value == 100 -%} + {%- assign percent = "%" -%} +{%- endif -%} + +{%- assign great_range = "0" | append: "-" | append: warning_start | append: percent -%} +{%- assign caution_range = warning_start | append: "-" | append: danger_start | append: percent -%} +{%- assign danger_range = danger_start | append: "+" -%} +{%- if max_value == 100 -%} + {%- assign danger_range = danger_start | append: "-" | append: "100%" -%} +{%- endif -%} + + +
+ {%- for item in data -%} + {%- assign name = item.name | capitalize -%} + {%- assign value = item.value -%} + {%- if max_value == 100 -%} + {%- assign value = item.value | round: 4 | divided_by: max_value_int | times: 10000 -%} + {%- endif -%} + + {%- assign accuracy = "no data" -%} + {%- if item.accuracy != "no data" -%} + {%- assign accuracy = item.accuracy | times: 100 | round: 1 | append: "%" -%} + {%- endif -%} + + {%- capture accuracy_tooltip -%} +
+ accuracy:{{accuracy}} +
+ {%- endcapture -%} + {%- if item.accuracy -%} + {%- assign accuracy_tooltip = "" -%} + {%- endif -%} + + {%- assign color = "success" -%} + {%- assign status = "great!" -%} + {%- if value > danger_start -%} + {%- assign color = "danger" -%} + {%- assign status = "danger!" -%} + {%- elsif value > warning_start -%} + {%- assign color = "warning" -%} + {%- assign status = "caution" -%} + {%- endif -%} + +
+ +
+
+
+
+
+
+
+ {%- endfor -%} +
+ + +{%- if include.data_attribute -%} +

+ {{include.data_attribute | markdownify | remove: "

" | remove: "

" | remove: "."}} +

+{%- endif -%} + + + diff --git a/_includes/components/details-api.html b/_includes/components/details-api.html new file mode 100644 index 0000000..82d4b6f --- /dev/null +++ b/_includes/components/details-api.html @@ -0,0 +1,94 @@ + + +{%- assign data_id = include.id | append: "Data" -%} +{%- assign data_folder = include.data.data.data_folder | remove: "site/" | remove: "_data/" | remove: "data/" | split: "/" -%} +{%- assign data_path = site.data -%} +{%- for item in data_folder -%} + {%- assign data_path = data_path[item] -%} +{%- endfor -%} +{%- assign dataset = include.data.data.datasets | first -%} +{%- assign sample_data = data_path[dataset.data_file_name] | last -%} + +{%- assign data_sources = "" -%} +{%- for item in include.data.data.datasets -%} + {%- assign data_sources = data_sources | append: item["data_source"] -%} + {%- unless forloop.last -%} + {%- assign data_sources = data_sources | append: ", " -%} + {%- endunless -%} +{%- endfor -%} + +{%- capture content -%} +
+
+
+ https://stateofeth.com/api/v1/validator-consensus-client-diversity/[data_source].json +
+ + {{site.data.icons.copy}} + +
+
Replace [data_source] with the name of the data source you're interested in: {{data_sources}}
+
+ + + +{:class="table mb-3"} +Key | Type | Description +----------|--------|-------------------------- +date | string | The date in yyyy-mm-dd format of when the data was updated. +timestamp | int | The Unix timestamp of when the data was updated. +data | object | An object containing all the data. + + +Example response: + + +
+

+  
+    {{site.data.icons.copy}}
+  
+
+ +{%- endcapture -%} + + +{% include components/details.html + title=include.title + body=content + open=include.open +%} + + diff --git a/_includes/components/details-data-methodology.html b/_includes/components/details-data-methodology.html new file mode 100644 index 0000000..3be271d --- /dev/null +++ b/_includes/components/details-data-methodology.html @@ -0,0 +1,17 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture content -%} + {{include.data_attribute | markdownify | append: include.data_methodology }} +{%- endcapture -%} + + +{% include components/details.html + title="Data Source & Methodology" + body=content +%} diff --git a/_includes/components/details-historical-chart.html b/_includes/components/details-historical-chart.html new file mode 100644 index 0000000..24d8870 --- /dev/null +++ b/_includes/components/details-historical-chart.html @@ -0,0 +1,25 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture content -%} + {% include components/data-line-chart.html + chart_id=include.chart_id + data=include.data + data_obj_key=include.data_obj_key + data_attribute=include.data_attribute + %} +{%- endcapture -%} + + +{% include components/details.html + title="Historical Data" + body=content +%} diff --git a/_includes/components/details.html b/_includes/components/details.html new file mode 100644 index 0000000..2b92786 --- /dev/null +++ b/_includes/components/details.html @@ -0,0 +1,32 @@ +{%- comment -%} + +{%- endcomment -%} + +
+
+ {%- assign open = "" -%} + {%- if include.open -%} + {%- assign open = "open" -%} + {%- endif -%} + {%- assign disabled = "" -%} + {%- if include.disabled -%} + {%- assign disabled = "disabled" -%} + {%- endif -%} +
+ {{include.title}} + + +
+
+ {{include.body | markdownify}} +
+
+
+
diff --git a/_includes/components/head.html b/_includes/components/head.html new file mode 100644 index 0000000..9c7aba8 --- /dev/null +++ b/_includes/components/head.html @@ -0,0 +1,86 @@ +{%- assign title = site.title -%} + {%- if page.title -%} + {%- capture page_title -%}{{ page.title }} | {{ title }}{%- endcapture -%} + {%- assign title = page_title -%} + {%- endif -%} + +{%- assign description = site.description -%} + {%- if page.description -%} + {%- assign description = page.description -%} + {%- endif -%} + +{%- assign keywords = site.keywords -%} + {%- if page.keywords -%} + {%- assign keywords = page.keywords -%} + {%- endif -%} + + + + + + + + + + + + + {{title}} + + + + + {%- assign favicon = "/assets/img/stateofeth/icons" -%} + + + + + + + + + + + {%- assign cover = site.url | append: "/assets/img/stateofeth/cover/cover.png" -%} + + + + + + + + + + + + + + + + + + + + + + diff --git a/_includes/components/nav-menu-mobile.html b/_includes/components/nav-menu-mobile.html new file mode 100644 index 0000000..234d6a3 --- /dev/null +++ b/_includes/components/nav-menu-mobile.html @@ -0,0 +1,41 @@ + + + +
+ +
Menu
+
+
+
+ +
+
+ {%- include components/nav-menu.html -%} +
+
diff --git a/_includes/components/nav-menu.html b/_includes/components/nav-menu.html new file mode 100644 index 0000000..9ac449d --- /dev/null +++ b/_includes/components/nav-menu.html @@ -0,0 +1,266 @@ + + + +{%- assign active_subcategory = "" -%} +{%- for category in site.data.nav-menu -%} + {%- for item in category.items -%} + {%- for subitem in item.sub_items -%} + {%- if subitem.link == page.permalink -%} + {%- assign active_subcategory = item.subcategory -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} +{%- endfor -%} + + + diff --git a/_includes/components/section-data.html b/_includes/components/section-data.html new file mode 100644 index 0000000..1443ea2 --- /dev/null +++ b/_includes/components/section-data.html @@ -0,0 +1,66 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- assign metric_id = page.id -%} +{%- assign data_folder = include.content.data.data_folder | remove: "site/" | remove: "_data/" | remove: "data/" | split: "/" -%} +{%- assign data_path = site.data -%} +{%- for item in data_folder -%} + {%- assign data_path = data_path[item] -%} +{%- endfor -%} + + +{%- capture content -%} + {% include components/select-data-source.html + metric_id=metric_id + data=include.content + %} + + {%- for dataset in include.content.data.datasets -%} + {%- assign chart_id = metric_id | append: dataset.data_source -%} + + {%- assign visibility = "" -%} + {%- if forloop.index > 1 -%} + {%- assign visibility = "d-none" -%} + {%- endif -%} + +
+ {%- assign data = data_path[dataset.data_file_name] -%} + {% include components/data-progress-bars.html + data=data + data_source=dataset.data_source + data_attribute=dataset.data_attribute + content=include.content + %} + + {%- assign data = data_path[dataset.data_file_name] -%} + {%- if dataset.historical_chart -%} + {% include components/details-historical-chart.html + chart_id=chart_id + data=data + data_attribute=dataset.data_attribute + data_obj_key=dataset.data_obj_key + %} + {%- else -%} + {% include components/details.html + title="Historical Data (Unavailable)" + disabled=true + %} + {%- endif -%} + + {% include components/details-data-methodology.html + data_attribute=dataset.data_attribute + data_methodology=dataset.data_methodology + %} +
+ {%- endfor -%} +{%- endcapture -%} + + +{% include components/card-text.html + title="Data" + body=content +%} diff --git a/_includes/components/section-description.html b/_includes/components/section-description.html new file mode 100644 index 0000000..27cfaea --- /dev/null +++ b/_includes/components/section-description.html @@ -0,0 +1,10 @@ +{%- comment -%} + +{%- endcomment -%} + +{% include components/card-msg.html + body=include.content.description +%} diff --git a/_includes/components/section-health-dashboard.html b/_includes/components/section-health-dashboard.html new file mode 100644 index 0000000..7b548af --- /dev/null +++ b/_includes/components/section-health-dashboard.html @@ -0,0 +1,397 @@ + + +{%- capture content -%} + +
+ + + + + + + + + + + + {% for item in site.data.dashboards.health %} + {%- assign title = item.title -%} + {%- assign link = item.link -%} + {%- assign page_info = site.pages | where: 'id', item.id | first -%} + {%- if page_info -%} + {%- if page_info.health_title -%} + {%- assign title = page_info.health_title -%} + {%- else -%} + {%- assign title = page_info.title -%} + {%- endif -%} + {%- if page_info.permalink -%} + {%- assign link = page_info.permalink -%} + {%- else -%} + {%- assign link = page_info.url -%} + {%- endif -%} + {%- endif -%} + + + + + + {%- assign disabled = "" -%} + {%- assign btn_color = "primary" -%} + {%- assign tooltip = "" -%} + {%- unless link and item.disabled != true -%} + {%- assign disabled = "disabled" -%} + {%- assign btn_color = "outline-secondary" -%} + {%- capture tooltip -%} + data-bs-toggle="tooltip" + data-bs-placement="top" + data-bs-html="true" + data-bs-original-title=" +
+
A page for this metric has not been created yet.
+
" + {%- endcapture -%} + {%- endunless -%} + + + {% endfor %} + +
+ {{"" | tooltip: "Favorite a metric to add it to your watchlist dashboard."}} + + {{site.data.icons.sort}} + + Metric + {{site.data.icons.sort}} + + Health + {{site.data.icons.sort}} +
+ {{site.data.icons.star_fill}} + + {{site.data.icons[item.icon]}} + + {%- capture badge -%} + SOON™ + {%- endcapture -%} + {%- assign color = "text-secondary" -%} + {%- if item.health or link -%} + {%- assign badge = "" -%} + {%- assign color = "" -%} + {%- endif -%} + {%- if item.disabled -%} + {%- assign badge = "" -%} + {%- assign color = "" -%} + {%- endif -%} +
+ {{badge}} + {{title}} +
+
+ {%- if item.health and item.disabled != true -%} + {%- capture health_value -%} + {%- include components/calculate_health.html id="consensus-client-diversity-validators" -%} + {%- endcapture -%} + {%- assign health_value = health_value | strip -%} + + {%- assign color = "warning" -%} + {%- assign state = "Caution" -%} + {%- if item.health >= 80 -%} + {%- assign color = "success" -%} + {%- assign state = "Great" -%} + {%- elsif item.health < 50 -%} + {%- assign color = "danger" -%} + {%- assign state = "Danger" -%} + {%- endif -%} +
+
+
+
+
+
+ {%- else -%} +
+
+
+ {%- endif -%} +
+ +
+
+ + +{%- capture favoriting_help_text -%} + To add a metric to your watchlist, click the {{site.data.icons.star_fill | addclass: "help-star"}} icon in the [Health Overview Dashboard](/dashboards/overview). +{%- endcapture -%} +{%- if include.dashboard == "favorites" -%} +
{{favoriting_help_text | markdownify}}
+{%- endif -%} + + + +{%- endcapture -%} + + +{% include components/card-text.html + title="Health" + body=content + tooltip="The health value is designed to provide a bird’s eye view on the general health of each metric in a normalized form." +%} diff --git a/_includes/components/section-health.html b/_includes/components/section-health.html new file mode 100644 index 0000000..e017bd3 --- /dev/null +++ b/_includes/components/section-health.html @@ -0,0 +1,58 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture health_value -%} + {%- include components/calculate_health.html id="consensus-client-diversity-validators" -%} +{%- endcapture -%} + + +{%- capture title -%} + Health - {{health_value | strip}}% +{%- endcapture -%} + + +{%- capture content -%} + The health ratings are designed to provide a bird's eye view on the general health of each metric in a normalized scale and format. + + To normalize the metric data for the health rating, there's a few steps that need to be taken. + + 1. Take the top value from the metric data, such as the highest marketshare/diversity item. This will be referred to at the "metric value" + 1. fdfd + + + his is calculated by taking the value of each metric, looking at its position within the current status range, and transposing that onto the normalized scale. Let's take a look at the example below: + + + + For Metric 1, you can see the scale is inverted, the status ranges are adjusted, and the meter is still positioned proportionally within the "caution" range (with inversion due to the reversal of the scale). + + For Metric 2 the transition may be easier to understand since a scale inversion isn't needed. + + +{%- endcapture -%} + +{%- capture content0 -%} + The health value is designed to provide a bird's eye view on the general health of each metric in a normalized format. + + This is calculated by taking the value of each metric, looking at its position within the current status range, and transposing that onto the normalized scale. Let's take a look at the example below: + + + + For Metric 1, you can see the scale is inverted, the status ranges are adjusted, and the meter is still positioned proportionally within the "caution" range (with inversion due to the reversal of the scale). + + For Metric 2 the transition may be easier to understand since a scale inversion isn't needed. + + +{%- endcapture -%} + + +{% include components/details.html + title=title + body=content +%} + + diff --git a/_includes/components/section-resources.html b/_includes/components/section-resources.html new file mode 100644 index 0000000..d7c36c4 --- /dev/null +++ b/_includes/components/section-resources.html @@ -0,0 +1,28 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture content -%} +
+ {%- for list in include.data.resources -%} +
+ {%- capture resource_list -%} +**{{list.category}}** +{% for item in list.links %} + - [{{item.title}}]({{item.link}}) +{%- endfor -%} + {%- endcapture -%} + {{resource_list | markdownify}} +
+ {%- endfor -%} +
+{%- endcapture -%} + + +{% include components/card-text.html + title="Resources" + body=content +%} diff --git a/_includes/components/section-risks.html b/_includes/components/section-risks.html new file mode 100644 index 0000000..9d5a1bb --- /dev/null +++ b/_includes/components/section-risks.html @@ -0,0 +1,28 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture further_reading -%} + {% for item in include.content.further_reading %} +- [{{item.title}}]({{item.link}}) + {%- endfor -%} +{%- endcapture -%} + + +{%- capture content -%} +{{include.content.risks}} + +{% include components/details.html + title="Further reading..." + body=further_reading +%} +{%- endcapture -%} + + +{% include components/card-text.html + title="Risks" + body=content +%} diff --git a/_includes/components/section-sponsors.html b/_includes/components/section-sponsors.html new file mode 100644 index 0000000..5b7459a --- /dev/null +++ b/_includes/components/section-sponsors.html @@ -0,0 +1,67 @@ + + + +{%- capture content -%} +If you'd like to sponsor this site and feature your logo here, please reach out to hanniabu on [Discord](https://discordapp.com/users/373406915696394242) (direct profile link) or [Twitter](https://twitter.com/hanni_abu). +{%- endcapture -%} + + +{%- assign sponsors = site.data.sponsors -%} +
+
+

Sponsors

+
+ {%- for i in (1..3) -%} + {%- for sponsor in sponsors -%} + {%- if i == sponsor.tier -%} + + {%- endif -%} + {%- endfor -%} + {%- endfor -%} +
+ {%- if sponsors -%} +
+ {%- endif -%} + {{content | markdownify}} +
+
diff --git a/_includes/components/section-take-action.html b/_includes/components/section-take-action.html new file mode 100644 index 0000000..57d8e8f --- /dev/null +++ b/_includes/components/section-take-action.html @@ -0,0 +1,31 @@ +{%- comment -%} + +{%- endcomment -%} + +{%- capture content -%} + {%- for item in include.content.take_action -%} + {%- if item.content contains "partials/metrics" -%} + {%- capture include_content -%} + {%- include {{item.content}} -%} + {%- endcapture -%} + {% include components/details.html + title=item.title + body=include_content + %} + {%- else -%} + {% include components/details.html + title=item.title + body=item.content + %} + {%- endif -%} + {%- endfor -%} +{%- endcapture -%} + + +{% include components/card-text.html + title="Take Action!" + body=content +%} diff --git a/_includes/components/section-toc.html b/_includes/components/section-toc.html new file mode 100644 index 0000000..ff51c2b --- /dev/null +++ b/_includes/components/section-toc.html @@ -0,0 +1,40 @@ +{%- comment -%} + +{%- endcomment -%} + + + + +
+

+ {%- for item in include.content.toc -%} + {%- assign header = "" | append: item -%} + {{item}} + {%- endfor -%} +

+
diff --git a/_includes/components/select-data-source.html b/_includes/components/select-data-source.html new file mode 100644 index 0000000..01d1577 --- /dev/null +++ b/_includes/components/select-data-source.html @@ -0,0 +1,17 @@ +{%- assign metric_id = include.metric_id -%} +{%- assign data = include.data -%} +
+ +
diff --git a/_includes/components/toast.html b/_includes/components/toast.html new file mode 100644 index 0000000..7c55804 --- /dev/null +++ b/_includes/components/toast.html @@ -0,0 +1,87 @@ +{%- comment -%} + +{%- endcomment -%} + + +{%- if site.enable_toast == true -%} + + + + +
+ +
+ + + +{%- endif -%} diff --git a/_includes/components/watermark.html b/_includes/components/watermark.html new file mode 100644 index 0000000..80f86d3 --- /dev/null +++ b/_includes/components/watermark.html @@ -0,0 +1,15 @@ + + +
+ StateOfEth.com +
diff --git a/_includes/partials/404.html b/_includes/partials/404.html new file mode 100644 index 0000000..56c7b35 --- /dev/null +++ b/_includes/partials/404.html @@ -0,0 +1,7 @@ +{%- capture content -%} +Page not found :( +{%- endcapture -%} + +{% include components/card-msg.html + body=content +%} \ No newline at end of file diff --git a/_includes/partials/about/about-state-of-eth.html b/_includes/partials/about/about-state-of-eth.html new file mode 100644 index 0000000..facb7a2 --- /dev/null +++ b/_includes/partials/about/about-state-of-eth.html @@ -0,0 +1,11 @@ +{%- capture content -%} +State of Eth aims to bridge the gap between data and education, serving as a rallying point for the social layer to promote positive change within the Ethereum ecosystem. + +This site is developed and maintained by [hanniabu.eth](https://twitter.com/hanni_abu). +{%- endcapture -%} + + +{% include components/card-text.html + title="About State of Eth" + body=content +%} diff --git a/_includes/partials/about/contact.html b/_includes/partials/about/contact.html new file mode 100644 index 0000000..05996f6 --- /dev/null +++ b/_includes/partials/about/contact.html @@ -0,0 +1,9 @@ +{%- capture content -%} +You can contact hanniabu on [Discord](https://discordapp.com/users/373406915696394242) (direct profile link) or [Twitter](https://twitter.com/hanni_abu). +{%- endcapture -%} + + +{% include components/card-text.html + title="Contact" + body=content +%} diff --git a/_includes/partials/about/inspiration.html b/_includes/partials/about/inspiration.html new file mode 100644 index 0000000..22a6ec5 --- /dev/null +++ b/_includes/partials/about/inspiration.html @@ -0,0 +1,11 @@ +{%- capture content -%} +The inspiration for State of Eth was arrived at from the creation of [ClientDiversity.org](https://clientdiversity.org/) and wanting to highlight other important metrics. [EthSunshine.com](https://ethsunshine.com/) became a proof of concept for that desire. After taking in learnings from the prior initiatives, the next iteration evolved into State of Eth. + +UI design inspiration heavily borrowed from [DefiLlama](https://defillama.com/). +{%- endcapture -%} + + +{% include components/card-text.html + title="Inspiration" + body=content +%} diff --git a/_includes/partials/content/index.html b/_includes/partials/content/index.html deleted file mode 100644 index 7365e2d..0000000 --- a/_includes/partials/content/index.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/_includes/partials/donate/direct-donations.html b/_includes/partials/donate/direct-donations.html new file mode 100644 index 0000000..493e6cd --- /dev/null +++ b/_includes/partials/donate/direct-donations.html @@ -0,0 +1,9 @@ +{%- capture content -%} +You can send any token, on any network, to [stateofeth.eth](https://etherscan.io/address/stateofeth.eth). +{%- endcapture -%} + + +{% include components/card-text.html + title="Direct donations" + body=content +%} diff --git a/_includes/partials/donate/gitcoin.html b/_includes/partials/donate/gitcoin.html new file mode 100644 index 0000000..33007f2 --- /dev/null +++ b/_includes/partials/donate/gitcoin.html @@ -0,0 +1,9 @@ +{%- capture content -%} +Donations on Gitcoin get matched with donations from a quadratic funding pool, so a small 1$ donation can get heavily amplified. +{%- endcapture -%} + + +{% include components/card-text.html + title="Gitcoin" + body=content +%} diff --git a/_includes/partials/donate/use-of-funds.html b/_includes/partials/donate/use-of-funds.html new file mode 100644 index 0000000..3be29a3 --- /dev/null +++ b/_includes/partials/donate/use-of-funds.html @@ -0,0 +1,11 @@ +{%- capture content -%} +Funds will be used for 2 purposes: +- Support development of new features and maintenance +- Fund grants for research and data sources +{%- endcapture -%} + + +{% include components/card-text.html + title="Use of funds" + body=content +%} diff --git a/_includes/partials/donate/why-donate.html b/_includes/partials/donate/why-donate.html new file mode 100644 index 0000000..8e2d1e7 --- /dev/null +++ b/_includes/partials/donate/why-donate.html @@ -0,0 +1,9 @@ +{%- capture content -%} +State of Eth is an open-source project and provides all data for free. Ongoing development and maintenance is supported by donations. +{%- endcapture -%} + + +{% include components/card-text.html + title="Why donate?" + body=content +%} diff --git a/_includes/partials/metrics/consensus-client-diversity-validators/client-info.html b/_includes/partials/metrics/consensus-client-diversity-validators/client-info.html new file mode 100644 index 0000000..900d77a --- /dev/null +++ b/_includes/partials/metrics/consensus-client-diversity-validators/client-info.html @@ -0,0 +1,75 @@ +{%- capture table -%} + +{:class="table"} +Client | Resources | Status | Support | Language | Donate** +-------|-----------|--------|---------|----------|-------- +{% for client in site.data.metrics.consensus-client-diversity-validators.client-info %} + {%- assign client_name = client.name -%} + {%- if client.name == "Grandine" -%} + {%- assign client_name = "*Grandine" -%} + {%- endif -%} + {%- capture client_link -%} + {%- if client.link -%} + [{{client_name}}]({{client.link}}) + {%- else -%} + {{client_name}} + {%- endif -%} + {%- endcapture -%} + {%- capture resources -%} + {%- if client.github and client.docs and client.chat -%} + [Github]({{client.github}}), [Docs]({{client.docs}}), [Chat]({{client.chat}}) + {%- elsif client.github and client.docs -%} + [Github]({{client.github}}), [Docs]({{client.docs}}) + {%- elsif client.github and client.chat -%} + [Github]({{client.github}}), [Chat]({{client.chat}}) + {%- elsif client.docs and client.chat -%} + [Docs]({{client.docs}}), [Chat]({{client.chat}}) + {%- elsif client.github -%} + [Github]({{client.github}}) + {%- elsif client.docs -%} + [Docs]({{client.docs}}) + {%- elsif client.chat -%} + [Chat]({{client.chat}}) + {%- else -%} + - + {%- endif -%} + {%- endcapture -%} + {%- assign status = "-" -%} + {%- if client.status -%} + {%- assign status = client.status -%} + {%- endif -%} + {%- assign support = "-" -%} + {%- if client.support -%} + {%- assign support = client.support -%} + {%- endif -%} + {%- assign lang = "-" -%} + {%- if client.lang -%} + {%- assign lang = client.lang -%} + {%- endif -%} + {%- capture donate -%} + {%- if client.donate == "funded" -%} + Funded + {%- elsif client.donate -%} + [Link]({{client.donate}}) + {%- else -%} + - + {%- endif -%} + {%- endcapture -%} +{{client_link}} | {{resources}} | {{status}} | {{support}} | {{lang}} | {{donate}} +{% endfor %} + +{%- endcapture -%} + + +{%- capture caption -%} +\* Grandine is not open sourced + +\*\* Donations made to [Protocol Guild](https://protocol-guild.readthedocs.io/en/latest/index.html) are distributed among Ethereum protocol contributors, including client teams. All recipients and splits can be [seen here](https://protocol-guild.readthedocs.io/en/latest/9-membership.html). +{%- endcapture -%} + + +{% include components/card-table.html + title="Client Info" + table=table + caption=caption +%} diff --git a/_includes/partials/metrics/consensus-client-diversity-validators/take-action/client-migration-manual.html b/_includes/partials/metrics/consensus-client-diversity-validators/take-action/client-migration-manual.html new file mode 100644 index 0000000..a359d10 --- /dev/null +++ b/_includes/partials/metrics/consensus-client-diversity-validators/take-action/client-migration-manual.html @@ -0,0 +1,148 @@ +

Migrate to a minority client using step-by-step guides.

+ +
+ + +
+
+ + +
+Search Guides +
+
+

Error: Select both To and From clients

+
+
+

There are no guides for this migration yet.

+
+
+ +
+ + + diff --git a/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-auto.html b/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-auto.html new file mode 100644 index 0000000..a0f7520 --- /dev/null +++ b/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-auto.html @@ -0,0 +1,10 @@ +{%- capture content -%} +Migrate to a minority client using [Ethereum Client Switcher](https://github.com/accidental-green/client-switcher), an open source tool that helps instantly switch your execution client (Geth, Besu, Nethermind). This tool was developed by Accidental Green. + +**Note**: The switcher assumes a "standard" installation based on [Somer Esat's Guides](https://github.com/SomerEsat/ethereum-staking-guides). +{%- endcapture -%} + +{% include components/details.html + title="Node Operators - Automatic Client Migration" + body=content +%} diff --git a/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-manual.html b/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-manual.html new file mode 100644 index 0000000..be6b637 --- /dev/null +++ b/_includes/partials/metrics/execution-client-diversity-validators/take-action/client-migration-manual.html @@ -0,0 +1,158 @@ +{%- capture content -%} + +

Migrate to a minority client using step-by-step guides.

+ +
+ + +
+
+ + +
+Search Guides +
+
+

Error: Select both To and From clients

+
+
+

There are no guides for this migration yet.

+
+
+ +
+ +{%- endcapture -%} + + +{% include components/details.html + title="Node Operators - Manual Client Migration Guides" + body=content +%} + + + diff --git a/_includes/tools/get-content.html b/_includes/tools/get-content.html deleted file mode 100644 index 9f80074..0000000 --- a/_includes/tools/get-content.html +++ /dev/null @@ -1,60 +0,0 @@ -{%- comment -%} - -{%- endcomment -%} - - -{%- assign content_url = include.content_url -%} -{%- assign from = include.from -%} -{%- assign to = include.to -%} - -{%- assign exclude_from = true -%} -{%- if include.exclude_from -%} - {%- assign exclude_from = include.exclude_from -%} -{%- endif -%} - -{%- assign exclude_to = true -%} -{%- if include.exclude_to -%} - {%- assign exclude_to = include.exclude_to -%} -{%- endif -%} - -{%- capture contents -%} - {% include_file {{include.content_url}} %} -{%- endcapture -%} - - -{%- if exclude_from == true and exclude_to == true -%} - {%- assign contents = contents | split: from | last | split: to | first -%} - {%- assign contents_debug = "exclude_from = true, exclude_to = true" -%} -{%- endif -%} - -{%- if exclude_from != true and exclude_to != true -%} - {%- assign contents = contents | split: from | last | prepend: from | split: to | first | append: to -%} - {%- assign contents_debug = "exclude_from = false, exclude_to = false" -%} -{%- endif -%} - -{%- if exclude_from == true and exclude_to != true -%} - {%- assign contents = contents | split: from | last | split: to | first | append: to -%} - {%- assign contents_debug = "exclude_from = true, exclude_to = false" -%} -{%- endif -%} - -{%- if exclude_from != true and exclude_to == true -%} - {%- assign contents = contents | split: from | last | prepend: from | split: to | first -%} - {%- assign contents_debug = "exclude_from = false, exclude_to = true" -%} -{%- endif -%} - - -{{contents}} diff --git a/_layouts/default.html b/_layouts/default.html index dee15a2..06a6da9 100755 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -1,6 +1,23 @@ - - - {{content}} + + {%- include components/head.html -%} + + {%- include components/toast.html -%} + + {%- include components/nav-menu-mobile.html -%} +
+ {% include components/card-alert.html + title="This is test data, it is not representative of the real data." + type="warning" + %} + {%- if page.title -%} +

{{page.title}}

+ {%- endif -%} + {{content}} + {%- include components/section-sponsors.html -%} +
+ \ No newline at end of file diff --git a/_scripts/collect_data.py b/_scripts/collect_data.py new file mode 100644 index 0000000..6342168 --- /dev/null +++ b/_scripts/collect_data.py @@ -0,0 +1,772 @@ +import utilities +import requests +import os +import math +import time +import json +import copy +import pprint +from datetime import datetime, timezone +from consensus_client_diversity_validators.migalabs import migalabs_marketshare + + +######################################## + + + +def get_rated_overview_data(): + if utilities.use_test_data: + # response split into multiple lines so it can be collapsed + response = { + 'status': 200, 'attempts': 1, 'data': [ + {'timeWindow': 'all', 'validatorCount': 732484, 'validatorCountDiff': 0, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 0, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': 0.0, 'consensusLayerRewardsPercentage': 70.89001418712094, 'priorityFeesPercentage': 22.444404751981917, 'baselineMevPercentage': 6.665581060897138, 'avgValidatorEffectiveness': 96.34926615541492, 'avgInclusionDelay': 1.02674142057442, 'avgUptime': 99.64490933047226, 'sumMissedSlots': 72749, 'missedSlotsPercentage': 1.022675595110489, 'avgConsensusAprPercentage': 3.5752906143040857, 'avgExecutionAprPercentage': 1.4681427314232345, 'medianConsensusAprPercentage': 3.466286508072917, 'medianExecutionAprPercentage': 0.4706290926215278, 'consensusRewardsRatio': 0.7089001418712094, 'executionRewardsRatio': 0.2910998581287906, 'avgNetworkAprPercentage': 5.04343334572732, 'medianNetworkAprPercentage': 3.936915600694445, 'avgConsensusAprGwei': 1144092997, 'avgExecutionAprGwei': 469805674, 'medianConsensusAprGwei': 1109211683, 'medianExecutionAprGwei': 150601310, 'avgNetworkAprGwei': 1613898671, 'medianNetworkAprGwei': 1259812992, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '7d', 'validatorCount': 732484, 'validatorCountDiff': 15285, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 489120000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': -1584026.892314911, 'consensusLayerRewardsPercentage': 74.17879638365959, 'priorityFeesPercentage': 18.16255691339244, 'baselineMevPercentage': 7.658646702947978, 'avgValidatorEffectiveness': 96.96456915550357, 'avgInclusionDelay': 1.0233240107783756, 'avgUptime': 99.5917567250837, 'sumMissedSlots': 356, 'missedSlotsPercentage': 0.7063492063492063, 'avgConsensusAprPercentage': 3.3712964163931844, 'avgExecutionAprPercentage': 1.1735284941601334, 'medianConsensusAprPercentage': 2.8652880479910716, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.7417879638365958, 'executionRewardsRatio': 0.25821203616340416, 'avgNetworkAprPercentage': 4.544824910553318, 'medianNetworkAprPercentage': 2.8652880479910716, 'avgConsensusAprGwei': 1078814853, 'avgExecutionAprGwei': 375529118, 'medianConsensusAprGwei': 916892175, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1454343971, 'medianNetworkAprGwei': 916892175, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '30d', 'validatorCount': 732484, 'validatorCountDiff': 60820, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 1946240000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': -2782230.2838668823, 'consensusLayerRewardsPercentage': 70.19038984546984, 'priorityFeesPercentage': 20.892577953835676, 'baselineMevPercentage': 8.917032200694484, 'avgValidatorEffectiveness': 97.08324599003156, 'avgInclusionDelay': 1.0218653815464485, 'avgUptime': 99.60600150408419, 'sumMissedSlots': 1589, 'missedSlotsPercentage': 0.7356481481481482, 'avgConsensusAprPercentage': 3.4431767042864805, 'avgExecutionAprPercentage': 1.462304960464106, 'medianConsensusAprPercentage': 2.9132289458333336, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.7019038984546984, 'executionRewardsRatio': 0.29809610154530164, 'avgNetworkAprPercentage': 4.905481664750587, 'medianNetworkAprPercentage': 2.9132289458333336, 'avgConsensusAprGwei': 1101816545, 'avgExecutionAprGwei': 467937587, 'medianConsensusAprGwei': 932233263, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1569754133, 'medianNetworkAprGwei': 932233263, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '1d', 'validatorCount': 732484, 'validatorCountDiff': 2183, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 69856000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': 220670.82093429565, 'consensusLayerRewardsPercentage': 68.76553541277683, 'priorityFeesPercentage': 22.120531489097694, 'baselineMevPercentage': 9.113933098125479, 'avgValidatorEffectiveness': 97.30381474144272, 'avgInclusionDelay': 1.0206948586335172, 'avgUptime': 99.64239298136275, 'sumMissedSlots': 51, 'missedSlotsPercentage': 0.7083333333333333, 'avgConsensusAprPercentage': 3.3542611110708775, 'avgExecutionAprPercentage': 1.5235619014838189, 'medianConsensusAprPercentage': 2.8587050937500003, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.6876553541277683, 'executionRewardsRatio': 0.3123446458722317, 'avgNetworkAprPercentage': 4.877823012554696, 'medianNetworkAprPercentage': 2.8587050937500003, 'avgConsensusAprGwei': 1073363556, 'avgExecutionAprGwei': 487539808, 'medianConsensusAprGwei': 914785630, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1560903364, 'medianNetworkAprGwei': 914785630, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}]} + utilities.print_data("fetch", response) + return response + else: + url = "https://api.rated.network/v0/eth/network/overview" + payload = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': utilities.rated_token + } + response = utilities.fetch_json(url, "GET", payload, headers) + return response + +def process_rated_overview_data(raw_data): + # get the validator count + validator_count = raw_data["data"][0]["validatorCount"] + utilities.print_data("processed", validator_count, "validator_count") + return validator_count + + +def get_rated_operator_pool_data(): + if utilities.use_test_data: + # response split into multiple lines so it can be collapsed + response = {'status': 200, 'attempts': 1, 'data': + + {'page': {'from': None, 'to': None, 'size': 100, 'granularity': None, 'filterType': None}, 'total': 31, 'data': [{'id': 'Lido', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 234187, 'avgCorrectness': 0.993726633441454, 'avgInclusionDelay': 1.022812108183317, 'avgUptime': 0.9988873921094028, 'avgValidatorEffectiveness': 97.12750897959496, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.37956359121125394}, {'client': 'Lodestar', 'percentage': 0.033033510501370406}, {'client': 'Nimbus', 'percentage': 0.03896182558791018}, {'client': 'Prysm', 'percentage': 0.3298051151592241}, {'client': 'Teku', 'percentage': 0.21863595754024137}], 'networkPenetration': 0.3215798659506014, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.1937044378002486}, {'relayer': 'manifold', 'percentage': 0.001310175697920516}, {'relayer': 'blocknative', 'percentage': 0.08862162797729028}, {'relayer': 'no_mev_boost', 'percentage': 0.018779185003527397}, {'relayer': 'bloxroute_regulated', 'percentage': 0.15198038095877986}, {'relayer': 'agnostic', 'percentage': 0.1553398058252427}, {'relayer': 'aestus', 'percentage': 0.04538582994591326}, {'relayer': 'edennetwork', 'percentage': 0.01404239594181476}, {'relayer': 'flashbots', 'percentage': 0.12164477441462021}, {'relayer': 'ultra_sound_money', 'percentage': 0.20915779218597777}, {'relayer': 'bloxroute_ethical', 'percentage': 3.359424866462861e-05}], 'nodeOperatorCount': 30, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Lido', 'aprPercentage': 4.59}, {'id': 'Coinbase', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 73434, 'avgCorrectness': 0.9948444204225114, 'avgInclusionDelay': 1.0202507882094498, 'avgUptime': 0.9982809511129033, 'avgValidatorEffectiveness': 97.3930362697948, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.3966019520700874}, {'client': 'Nimbus', 'percentage': 0.035681629702086035}, {'client': 'Prysm', 'percentage': 0.3422501966955153}, {'client': 'Teku', 'percentage': 0.22546622153231122}], 'networkPenetration': 0.10083777441197189, 'relayerPercentages': [{'relayer': 'ultra_sound_money', 'percentage': 0.24420383664822878}, {'relayer': 'blocknative', 'percentage': 0.013298811345180652}, {'relayer': 'aestus', 'percentage': 0.0024714605154760503}, {'relayer': 'no_mev_boost', 'percentage': 0.010591973637754501}, {'relayer': 'agnostic', 'percentage': 0.20760268329998824}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.36471695892668}, {'relayer': 'bloxroute_regulated', 'percentage': 0.040131811227492056}, {'relayer': 'flashbots', 'percentage': 0.11627633282334941}, {'relayer': 'manifold', 'percentage': 0.00023537719195010004}, {'relayer': 'edennetwork', 'percentage': 0.0004707543839002001}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Coinbase', 'aprPercentage': 4.66}, {'id': 'Binance', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 41098, 'avgCorrectness': 0.9941272520338371, 'avgInclusionDelay': 1.0206046662702684, 'avgUptime': 0.9992489805629131, 'avgValidatorEffectiveness': 97.3858552000696, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.612957769712966}, {'client': 'Nimbus', 'percentage': 0.00024744308808973936}, {'client': 'Prysm', 'percentage': 0.386052457934675}, {'client': 'Teku', 'percentage': 0.0007423292642692181}], 'networkPenetration': 0.05643476935456629, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.03450465707027942}, {'relayer': 'blocknative', 'percentage': 0.06604572396274344}, {'relayer': 'agnostic', 'percentage': 0.04403048264182896}, {'relayer': 'ultra_sound_money', 'percentage': 0.29106689246401357}, {'relayer': 'bloxroute_regulated', 'percentage': 0.24174428450465707}, {'relayer': 'no_mev_boost', 'percentage': 0.006985605419136325}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.12235393734123624}, {'relayer': 'flashbots', 'percentage': 0.193268416596105}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Binance', 'aprPercentage': 4.72}, {'id': 'Kraken', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 24561, 'avgCorrectness': 0.9949079416133426, 'avgInclusionDelay': 1.0209231973227801, 'avgUptime': 0.9987906142765821, 'avgValidatorEffectiveness': 97.38967836162888, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.02180460185519817}, {'client': 'Nimbus', 'percentage': 0.00030116853391157694}, {'client': 'Prysm', 'percentage': 0.9413323695940248}, {'client': 'Teku', 'percentage': 0.03656186001686544}], 'networkPenetration': 0.033285775686278815, 'relayerPercentages': [{'relayer': 'blocknative', 'percentage': 0.05123398937831927}, {'relayer': 'aestus', 'percentage': 0.030927835051546393}, {'relayer': 'ultra_sound_money', 'percentage': 0.1836925960637301}, {'relayer': 'edennetwork', 'percentage': 0.010621680724773508}, {'relayer': 'flashbots', 'percentage': 0.32146204311152765}, {'relayer': 'no_mev_boost', 'percentage': 0.016869728209934397}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.1543267728834739}, {'relayer': 'agnostic', 'percentage': 0.22992814745392065}, {'relayer': 'manifold', 'percentage': 0.0009372071227741331}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Kraken', 'aprPercentage': 4.58}, {'id': 'Rocketpool', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 22256, 'avgCorrectness': 0.9927764902413493, 'avgInclusionDelay': 1.0271212800957081, 'avgUptime': 0.9830124807085743, 'avgValidatorEffectiveness': 95.14506199159602, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.41736339836162484}, {'client': 'Lodestar', 'percentage': 0.0029558314331559836}, {'client': 'Nimbus', 'percentage': 0.16848239168989107}, {'client': 'Prysm', 'percentage': 0.09610674774090026}, {'client': 'Teku', 'percentage': 0.3150916307744278}], 'networkPenetration': 0.03056139536608174, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.03410024650780608}, {'relayer': 'aestus', 'percentage': 0.0447822514379622}, {'relayer': 'bloxroute_ethical', 'percentage': 0.0045193097781429745}, {'relayer': 'bloxroute_regulated', 'percentage': 0.18323746918652423}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.1914543960558751}, {'relayer': 'flashbots', 'percentage': 0.16803615447822515}, {'relayer': 'ultra_sound_money', 'percentage': 0.18734593262119967}, {'relayer': 'blocknative', 'percentage': 0.1314708299096138}, {'relayer': 'no_mev_boost', 'percentage': 0.03327855382087099}, {'relayer': 'edennetwork', 'percentage': 0.021774856203779787}], 'nodeOperatorCount': 2118, 'operatorTags': [ + {'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Rocketpool', 'aprPercentage': 4.48}, {'id': 'Bitcoin Suisse', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 13725, 'avgCorrectness': 0.9934730782222934, 'avgInclusionDelay': 1.0227518067626231, 'avgUptime': 0.9992559053979818, 'avgValidatorEffectiveness': 97.12659904050217, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0002257336343115124}, {'client': 'Nimbus', 'percentage': 0.02528216704288939}, {'client': 'Prysm', 'percentage': 0.0012415349887133183}, {'client': 'Teku', 'percentage': 0.9732505643340857}], 'networkPenetration': 0.018846834624347227, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.15445232466509062}, {'relayer': 'ultra_sound_money', 'percentage': 0.16233254531126873}, {'relayer': 'edennetwork', 'percentage': 0.03664302600472813}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.2470449172576832}, {'relayer': 'bloxroute_regulated', 'percentage': 0.24152876280535854}, {'relayer': 'blocknative', 'percentage': 0.15642237982663515}, {'relayer': 'manifold', 'percentage': 0.0007880220646178094}, {'relayer': 'no_mev_boost', 'percentage': 0.0003940110323089047}, {'relayer': 'bloxroute_ethical', 'percentage': 0.0003940110323089047}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'custodian', 'path': None, 'idType': None}], 'displayName': 'Bitcoin Suisse', 'aprPercentage': 4.35}, {'id': 'Celsius Network', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 11146, 'avgCorrectness': 0.9956812266178313, 'avgInclusionDelay': 1.0234562032588206, 'avgUptime': 0.999289716006027, 'avgValidatorEffectiveness': 97.28277002636149, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0019646365422396855}, {'client': 'Nimbus', 'percentage': 0.0004365858982754857}, {'client': 'Prysm', 'percentage': 0.9965073128137961}, {'client': 'Teku', 'percentage': 0.0010914647456887142}], 'networkPenetration': 0.015305414843203947, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.018891687657430732}, {'relayer': 'flashbots', 'percentage': 0.9811083123425692}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Celsius Network', 'aprPercentage': 4.73}, {'id': 'Whale 0x5d76a', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 10603, 'avgCorrectness': 0.9954162547997246, 'avgInclusionDelay': 1.0190107360282337, 'avgUptime': 0.9999179028397173, 'avgValidatorEffectiveness': 97.72139614757302, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.01572052401746725}, {'client': 'Nimbus', 'percentage': 0.20058224163027658}, {'client': 'Prysm', 'percentage': 0.20844250363901018}, {'client': 'Teku', 'percentage': 0.575254730713246}], 'networkPenetration': 0.014559780511617751, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.0036798528058877645}, {'relayer': 'flashbots', 'percentage': 0.13615455381784727}, {'relayer': 'blocknative', 'percentage': 0.08187672493100276}, {'relayer': 'aestus', 'percentage': 0.09199632014719411}, {'relayer': 'ultra_sound_money', 'percentage': 0.17479300827966882}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.31922723091076355}, {'relayer': 'agnostic', 'percentage': 0.1922723091076357}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Whale 0x5d76a', 'aprPercentage': 4.64}, {'id': 'OKex', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 8331, 'avgCorrectness': 0.9943389739654295, 'avgInclusionDelay': 1.0196420801692967, 'avgUptime': 0.9998473988890638, 'avgValidatorEffectiveness': 97.55284068634985, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.00325821341297855}, {'client': 'Nimbus', 'percentage': 0.00027151778441487917}, {'client': 'Prysm', 'percentage': 0.9937550909584578}, {'client': 'Teku', 'percentage': 0.0027151778441487917}], 'networkPenetration': 0.01143992562881142, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.21678321678321677}, {'relayer': 'edennetwork', 'percentage': 0.03496503496503497}, {'relayer': 'ultra_sound_money', 'percentage': 0.13986013986013987}, {'relayer': 'blocknative', 'percentage': 0.12878787878787878}, {'relayer': 'flashbots', 'percentage': 0.11130536130536131}, {'relayer': 'bloxroute_regulated', 'percentage': 0.2191142191142191}, {'relayer': 'agnostic', 'percentage': 0.1486013986013986}, {'relayer': 'no_mev_boost', 'percentage': 0.0005827505827505828}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'OKex', 'aprPercentage': 4.67}, {'id': 'Ledger Live', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 7937, 'avgCorrectness': 0.9944569951463684, 'avgInclusionDelay': 1.020500085486865, 'avgUptime': 0.9998419963885397, 'avgValidatorEffectiveness': 97.48560668465123, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.4404320987654321}, {'client': 'Prysm', 'percentage': 0.558641975308642}, {'client': 'Teku', 'percentage': 0.000925925925925926}], 'networkPenetration': 0.010898894456352929, 'relayerPercentages': [{'relayer': 'manifold', 'percentage': 0.0006544502617801048}, {'relayer': 'blocknative', 'percentage': 0.09096858638743456}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.2212041884816754}, {'relayer': 'bloxroute_regulated', 'percentage': 0.2205497382198953}, {'relayer': 'agnostic', 'percentage': 0.14136125654450263}, {'relayer': 'ultra_sound_money', 'percentage': 0.17539267015706805}, {'relayer': 'edennetwork', 'percentage': 0.01832460732984293}, {'relayer': 'flashbots', 'percentage': 0.09424083769633508}, {'relayer': 'aestus', 'percentage': 0.03599476439790576}, {'relayer': 'no_mev_boost', 'percentage': 0.0013089005235602095}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'wallet', 'path': None, 'idType': None}], 'displayName': 'Ledger Live', 'aprPercentage': 4.55}, {'id': 'Frax', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 6168, 'avgCorrectness': 0.9958481017047883, 'avgInclusionDelay': 1.0195834230392993, 'avgUptime': 0.9998665298063167, 'avgValidatorEffectiveness': 97.70596901220054, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.5743181121667178}, {'client': 'Nimbus', 'percentage': 0.0009193993257738277}, {'client': 'Prysm', 'percentage': 0.4238430891817346}, {'client': 'Teku', 'percentage': 0.0009193993257738277}], 'networkPenetration': 0.008469746882548174, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.015283842794759825}, {'relayer': 'flashbots', 'percentage': 0.1517467248908297}, {'relayer': 'no_mev_boost', 'percentage': 0.007641921397379912}, {'relayer': 'agnostic', 'percentage': 0.4421397379912664}, {'relayer': 'ultra_sound_money', 'percentage': 0.38318777292576417}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Frax', 'aprPercentage': 4.82}, {'id': 'Wedex', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 3748, 'avgCorrectness': 0.9925165980726773, 'avgInclusionDelay': 1.0221779320624513, 'avgUptime': 0.9992166827599056, 'avgValidatorEffectiveness': 97.08032765692879, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.9966344131257888}, {'client': 'Nimbus', 'percentage': 0.0033655868742111907}], 'networkPenetration': 0.005146662016178754, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.21146245059288538}, {'relayer': 'blocknative', 'percentage': 0.0533596837944664}, {'relayer': 'flashbots', 'percentage': 0.07509881422924901}, {'relayer': 'no_mev_boost', 'percentage': 0.06521739130434782}, {'relayer': 'aestus', 'percentage': 0.01383399209486166}, {'relayer': 'ultra_sound_money', 'percentage': 0.2490118577075099}, {'relayer': 'agnostic', 'percentage': 0.3201581027667984}, {'relayer': 'edennetwork', 'percentage': 0.003952569169960474}, {'relayer': 'manifold', 'percentage': 0.007905138339920948}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Wedex', 'aprPercentage': 4.6}, {'id': 'Bitfinex', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 3373, 'avgCorrectness': 0.9921892630536591, 'avgInclusionDelay': 1.0272304908539234, 'avgUptime': 0.9989955717438671, 'avgValidatorEffectiveness': 96.5825094367404, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0018475750577367205}, {'client': 'Prysm', 'percentage': 0.9958429561200923}, {'client': 'Teku', 'percentage': 0.0023094688221709007}], 'networkPenetration': 0.004631721179447956, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.007722007722007722}, {'relayer': 'edennetwork', 'percentage': 0.003861003861003861}, {'relayer': 'no_mev_boost', 'percentage': 0.0694980694980695}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.04247104247104247}, {'relayer': 'agnostic', 'percentage': 0.02702702702702703}, {'relayer': 'flashbots', 'percentage': 0.7953667953667953}, {'relayer': 'ultra_sound_money', 'percentage': 0.03088803088803089}, {'relayer': 'blocknative', 'percentage': 0.023166023166023165}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Bitfinex', 'aprPercentage': 4.32}, {'id': 'StakeWise', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 2527, 'avgCorrectness': 0.9920072116347213, 'avgInclusionDelay': 1.0256374524814358, 'avgUptime': 0.9981566996139897, 'avgValidatorEffectiveness': 96.69471795229425, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.4250783699059561}, {'client': 'Nimbus', 'percentage': 0.015047021943573668}, {'client': 'Teku', 'percentage': 0.5598746081504702}], 'networkPenetration': 0.0034700146517832745, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.015037593984962405}, {'relayer': 'flashbots', 'percentage': 0.10902255639097744}, {'relayer': 'aestus', 'percentage': 0.03007518796992481}, {'relayer': 'agnostic', 'percentage': 0.3383458646616541}, {'relayer': 'ultra_sound_money', 'percentage': 0.23684210526315788}, {'relayer': 'no_mev_boost', 'percentage': 0.07142857142857142}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.09022556390977443}, {'relayer': 'blocknative', 'percentage': 0.10902255639097744}], 'nodeOperatorCount': 5, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'StakeWise', 'aprPercentage': 4.36}, {'id': 'StakeHound', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 1944, 'avgCorrectness': 0.9960434695745096, 'avgInclusionDelay': 1.0189132824123295, 'avgUptime': 0.9999363119733491, 'avgValidatorEffectiveness': 97.78297460142377, 'clientPercentages': [ + {'client': 'Nimbus', 'percentage': 0.025179856115107913}, {'client': 'Teku', 'percentage': 0.9748201438848921}], 'networkPenetration': 0.0026694532976124594, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.14473684210526316}, {'relayer': 'bloxroute_regulated', 'percentage': 0.22894736842105262}, {'relayer': 'aestus', 'percentage': 0.060526315789473685}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.21842105263157896}, {'relayer': 'agnostic', 'percentage': 0.15526315789473685}, {'relayer': 'edennetwork', 'percentage': 0.031578947368421054}, {'relayer': 'ultra_sound_money', 'percentage': 0.15789473684210525}, {'relayer': 'no_mev_boost', 'percentage': 0.002631578947368421}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'StakeHound', 'aprPercentage': 5.04}, {'id': 'Bitstamp', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 1924, 'avgCorrectness': 0.9940955477530529, 'avgInclusionDelay': 1.0208576706214647, 'avgUptime': 0.999556346421047, 'avgValidatorEffectiveness': 97.39111179379319, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0022371364653243847}, {'client': 'Prysm', 'percentage': 0.9962714392244594}, {'client': 'Teku', 'percentage': 0.0014914243102162564}], 'networkPenetration': 0.0026419897863201505, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.0223463687150838}, {'relayer': 'no_mev_boost', 'percentage': 0.013966480446927373}, {'relayer': 'aestus', 'percentage': 0.04189944134078212}, {'relayer': 'agnostic', 'percentage': 0.2709497206703911}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.20949720670391062}, {'relayer': 'ultra_sound_money', 'percentage': 0.22346368715083798}, {'relayer': 'blocknative', 'percentage': 0.10335195530726257}, {'relayer': 'flashbots', 'percentage': 0.11173184357541899}, {'relayer': 'manifold', 'percentage': 0.002793296089385475}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Bitstamp', 'aprPercentage': 5.85}, {'id': 'Huobi', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 1458, 'avgCorrectness': 0.9953170687839411, 'avgInclusionDelay': 1.0214802964666196, 'avgUptime': 0.9996390783458224, 'avgValidatorEffectiveness': 97.46528010550327, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.001638001638001638}, {'client': 'Prysm', 'percentage': 0.9975429975429976}, {'client': 'Teku', 'percentage': 0.000819000819000819}], 'networkPenetration': 0.0019718801107878044, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.02631578947368421}, {'relayer': 'blocknative', 'percentage': 0.12105263157894737}, {'relayer': 'bloxroute_regulated', 'percentage': 0.30526315789473685}, {'relayer': 'agnostic', 'percentage': 0.10526315789473684}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.23157894736842105}, {'relayer': 'flashbots', 'percentage': 0.08947368421052632}, {'relayer': 'ultra_sound_money', 'percentage': 0.12105263157894737}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Huobi', 'aprPercentage': 5.31}, {'id': 'Ankr', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 1327, 'avgCorrectness': 0.9948957478977004, 'avgInclusionDelay': 1.0207924767130718, 'avgUptime': 0.9014231865145435, 'avgValidatorEffectiveness': 87.90894649085247, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.7472527472527473}, {'client': 'Prysm', 'percentage': 0.2509157509157509}, {'client': 'Teku', 'percentage': 0.0018315018315018315}], 'networkPenetration': 0.0018029795163401025, 'relayerPercentages': [{'relayer': 'bloxroute_regulated', 'percentage': 0.13008130081300814}, {'relayer': 'flashbots', 'percentage': 0.11382113821138211}, {'relayer': 'ultra_sound_money', 'percentage': 0.18699186991869918}, {'relayer': 'agnostic', 'percentage': 0.2682926829268293}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.21951219512195122}, {'relayer': 'blocknative', 'percentage': 0.056910569105691054}, {'relayer': 'aestus', 'percentage': 0.024390243902439025}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Ankr', 'aprPercentage': 4.81}, {'id': 'Swell', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 1042, 'avgCorrectness': 0.994062122866175, 'avgInclusionDelay': 1.0210968505264826, 'avgUptime': 0.9990026076811737, 'avgValidatorEffectiveness': 97.30137089586893, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.5768194070080862}, {'client': 'Nimbus', 'percentage': 0.005390835579514825}, {'client': 'Prysm', 'percentage': 0.31805929919137466}, {'client': 'Teku', 'percentage': 0.09973045822102426}], 'networkPenetration': 0.0014308489383293122, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.02702702702702703}, {'relayer': 'blocknative', 'percentage': 0.10135135135135136}, {'relayer': 'bloxroute_regulated', 'percentage': 0.16216216216216217}, {'relayer': 'flashbots', 'percentage': 0.12837837837837837}, {'relayer': 'ultra_sound_money', 'percentage': 0.20945945945945946}, {'relayer': 'aestus', 'percentage': 0.0472972972972973}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.20270270270270271}, {'relayer': 'agnostic', 'percentage': 0.11486486486486487}, {'relayer': 'no_mev_boost', 'percentage': 0.006756756756756757}], 'nodeOperatorCount': 8, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Swell', 'aprPercentage': 4.35}, {'id': 'Kucoin', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 827, 'avgCorrectness': 0.9953127586432561, 'avgInclusionDelay': 1.0192378613306439, 'avgUptime': 0.9971747183355406, 'avgValidatorEffectiveness': 97.42383672142485, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0019120458891013384}, {'client': 'Prysm', 'percentage': 0.9980879541108987}], 'networkPenetration': 0.0011356161919369877, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.15841584158415842}, {'relayer': 'bloxroute_regulated', 'percentage': 0.1485148514851485}, {'relayer': 'no_mev_boost', 'percentage': 0.27722772277227725}, {'relayer': 'ultra_sound_money', 'percentage': 0.13861386138613863}, {'relayer': 'blocknative', 'percentage': 0.0594059405940594}, {'relayer': 'agnostic', 'percentage': 0.13861386138613863}, {'relayer': 'flashbots', 'percentage': 0.06930693069306931}, {'relayer': 'aestus', 'percentage': 0.009900990099009901}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Kucoin', 'aprPercentage': 4.48}, {'id': 'Whale 0xf805c', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 642, 'avgCorrectness': 0.994520218821616, 'avgInclusionDelay': 1.0220362845737097, 'avgUptime': 0.9988785046728973, 'avgValidatorEffectiveness': 97.26131687196829, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.0008815787124831271, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.05714285714285714}, {'relayer': 'no_mev_boost', 'percentage': 0.3142857142857143}, {'relayer': 'blocknative', 'percentage': 0.11428571428571428}, {'relayer': 'ultra_sound_money', 'percentage': 0.17142857142857143}, {'relayer': 'agnostic', 'percentage': 0.11428571428571428}, {'relayer': 'flashbots', 'percentage': 0.08571428571428572}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.12857142857142856}, {'relayer': 'manifold', 'percentage': 0.014285714285714285}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Whale 0xf805c', 'aprPercentage': 4.31}, {'id': 'Ether Capital', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 640, 'avgCorrectness': 0.9953092189598087, 'avgInclusionDelay': 1.0212603959236266, 'avgUptime': 0.9993710317460317, 'avgValidatorEffectiveness': 97.45606525567594, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.9433497536945813}, {'client': 'Nimbus', 'percentage': 0.0049261083743842365}, {'client': 'Teku', 'percentage': 0.05172413793103448}], 'networkPenetration': 0.0008788323613538962, 'relayerPercentages': [{'relayer': 'blocknative', 'percentage': 0.6724137931034483}, {'relayer': 'flashbots', 'percentage': 0.3275862068965517}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Ether Capital', 'aprPercentage': 4.65}, {'id': 'Wexexchange', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 500, 'avgCorrectness': 0.9957615424209793, 'avgInclusionDelay': 1.0183422394701018, 'avgUptime': 0.9999644444444445, 'avgValidatorEffectiveness': 97.81795755152838, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0031847133757961785}, {'client': 'Prysm', 'percentage': 0.9968152866242038}], 'networkPenetration': 0.0006865877823077314, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.03896103896103896}, {'relayer': 'agnostic', 'percentage': 0.19480519480519481}, {'relayer': 'blocknative', 'percentage': 0.14285714285714285}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.23376623376623376}, {'relayer': 'flashbots', 'percentage': 0.11688311688311688}, {'relayer': 'aestus', 'percentage': 0.09090909090909091}, {'relayer': 'ultra_sound_money', 'percentage': 0.18181818181818182}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Wexexchange', 'aprPercentage': 4.37}, {'id': 'Poloniex', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 400, 'avgCorrectness': 0.9955573123115258, 'avgInclusionDelay': 1.018503085170387, 'avgUptime': 0.9999190476190476, 'avgValidatorEffectiveness': 97.78388277551873, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.0005492702258461851, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.024390243902439025}, {'relayer': 'aestus', 'percentage': 0.07317073170731707}, {'relayer': 'flashbots', 'percentage': 0.08536585365853659}, {'relayer': 'blocknative', 'percentage': 0.10975609756097561}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.18292682926829268}, {'relayer': 'agnostic', 'percentage': 0.14634146341463414}, {'relayer': 'ultra_sound_money', 'percentage': 0.14634146341463414}, {'relayer': 'bloxroute_regulated', 'percentage': 0.23170731707317074}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'exchange', 'path': None, 'idType': None}], 'displayName': 'Poloniex', 'aprPercentage': 4.19}, {'id': 'Bitpie', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 363, 'avgCorrectness': 0.9943681321885328, 'avgInclusionDelay': 1.019147067673382, 'avgUptime': 0.9998303380121562, 'avgValidatorEffectiveness': 97.60111146928617, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.009230769230769232}, {'client': 'Prysm', 'percentage': 0.9907692307692307}], 'networkPenetration': 0.000498462729955413, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.9473684210526315}, {'relayer': 'no_mev_boost', 'percentage': 0.05263157894736842}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'wallet', 'path': None, 'idType': None}], 'displayName': 'Bitpie', 'aprPercentage': 7.04}, {'id': 'StaFi', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 277, 'avgCorrectness': 0.9961584967089632, 'avgInclusionDelay': 1.019033437310336, 'avgUptime': 0.9998313117515424, 'avgValidatorEffectiveness': 97.7882141536355, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.05855855855855856}, {'client': 'Prysm', 'percentage': 0.9414414414414415}], 'networkPenetration': 0.00038036963139848317, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.7}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.1}, {'relayer': 'blocknative', 'percentage': 0.1}, {'relayer': 'bloxroute_regulated', 'percentage': 0.05}, {'relayer': 'ultra_sound_money', 'percentage': 0.05}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'StaFi', 'aprPercentage': 4.28}, {'id': 'SharedStake', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 381, 'avgCorrectness': 0.9954268067765, 'avgInclusionDelay': 1.0185000752485416, 'avgUptime': 0.9993059777077634, 'avgValidatorEffectiveness': 97.67835032333734, 'clientPercentages': [{'client': 'Prysm', 'percentage': 0.9968354430379747}, {'client': 'Teku', 'percentage': 0.0031645569620253164}], 'networkPenetration': 0.0003103376776030946, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.17647058823529413}, {'relayer': 'ultra_sound_money', 'percentage': 0.4117647058823529}, {'relayer': 'agnostic', 'percentage': 0.4117647058823529}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'SharedStake', 'aprPercentage': 6.39}, {'id': 'Vitalik Buterin', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 218, 'avgCorrectness': 0.9943320931145032, 'avgInclusionDelay': 1.0207943033456772, 'avgUptime': 0.9996184651230523, 'avgValidatorEffectiveness': 97.4212966186359, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.9927007299270073}, {'client': 'Nimbus', 'percentage': 0.0072992700729927005}], 'networkPenetration': 0.0002993522730861709, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}, {'name': 'legendary', 'path': None, 'idType': None}], 'displayName': 'Vitalik Buterin', 'aprPercentage': 3.64}, {'id': 'Guarda', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 153, 'avgCorrectness': 0.9961027322556218, 'avgInclusionDelay': 1.0182636664883602, 'avgUptime': 0.5973524224504617, 'avgValidatorEffectiveness': 58.471275144606444, 'clientPercentages': [{'client': 'Prysm', 'percentage': 0.9906542056074766}, {'client': 'Teku', 'percentage': 0.009345794392523364}], 'networkPenetration': 0.0002100958613861658, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Guarda', 'aprPercentage': 1.38}, {'id': 'Bifrost', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 13, 'avgCorrectness': 0.9941211913951178, 'avgInclusionDelay': 1.0194440373247349, 'avgUptime': 0.9997069597069598, 'avgValidatorEffectiveness': 97.54026574059093, 'clientPercentages': [{'client': 'Prysm', 'percentage': 1.0}], 'networkPenetration': 1.7851282340001018e-05, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 1.0}], 'nodeOperatorCount': None, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'Bifrost', 'aprPercentage': 4.14}, {'id': 'pSTAKE', 'idType': 'pool', 'timeWindow': '7d', 'validatorCount': 2, 'avgCorrectness': 0.9959775590134434, 'avgInclusionDelay': 1.010161956176564, 'avgUptime': 0.9996825396825397, 'avgValidatorEffectiveness': 98.57697361376248, 'clientPercentages': [{'client': 'Prysm', 'percentage': 1.0}], 'networkPenetration': 2.7463511292309255e-06, 'relayerPercentages': [], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'pool', 'path': None, 'idType': None}], 'displayName': 'pSTAKE', 'aprPercentage': 2.88}], 'next': None}} + utilities.print_data("fetch", response) + return response + else: + url = "https://api.rated.network/v0/eth/operators?window=7d&idType=pool&size=100" + payload = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': utilities.rated_token + } + response = utilities.fetch_json(url, "GET", payload, headers) + return response + +def process_rated_operator_pool_data(raw_data): + pool_validator_counts = {} + for pool in raw_data["data"]["data"]: + pool_validator_counts[pool["id"]] = pool["validatorCount"] + utilities.print_data("processed", pool_validator_counts, "pool_validator_counts") + return pool_validator_counts + + +def get_rated_operator_node_data(): + if utilities.use_test_data: + # response split into multiple lines so it can be collapsed + response = {'status': 200, 'attempts': 1, 'data': { + + 'page': {'from': None, 'to': None, 'size': 100, 'granularity': None, 'filterType': None}, 'total': 52, 'data': [{'id': 'Kiln', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 23810, 'avgCorrectness': 0.9949509406954944, 'avgInclusionDelay': 1.017948702586212, 'avgUptime': 0.9998280828900458, 'avgValidatorEffectiveness': 97.76418156450255, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.333947045414694}, {'client': 'Nimbus', 'percentage': 0.0005260389268805891}, {'client': 'Prysm', 'percentage': 0.6386989303875154}, {'client': 'Teku', 'percentage': 0.026827985270910047}], 'networkPenetration': 0.032216076376861795, 'relayerPercentages': [{'relayer': 'blocknative', 'percentage': 0.09717868338557993}, {'relayer': 'edennetwork', 'percentage': 0.012763098969995522}, {'relayer': 'no_mev_boost', 'percentage': 0.0006717420510523958}, {'relayer': 'aestus', 'percentage': 0.025526197939991044}, {'relayer': 'ultra_sound_money', 'percentage': 0.17129422301836095}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.24406627854903717}, {'relayer': 'manifold', 'percentage': 0.001567398119122257}, {'relayer': 'bloxroute_regulated', 'percentage': 0.23510971786833856}, {'relayer': 'agnostic', 'percentage': 0.1334527541424093}, {'relayer': 'flashbots', 'percentage': 0.07836990595611286}], 'nodeOperatorCount': 4, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Kiln', 'aprPercentage': 4.93}, {'id': 'Allnodes', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 21117, 'avgCorrectness': 0.9962860938572455, 'avgInclusionDelay': 1.0169413849840505, 'avgUptime': 0.9999504713630141, 'avgValidatorEffectiveness': 97.99485409837003, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0004269854824935952}, {'client': 'Nimbus', 'percentage': 0.026643894107600343}, {'client': 'Prysm', 'percentage': 0.002049530315969257}, {'client': 'Teku', 'percentage': 0.9708795900939368}], 'networkPenetration': 0.028572317717353655, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.04551724137931035}, {'relayer': 'ultra_sound_money', 'percentage': 0.20379310344827586}, {'relayer': 'agnostic', 'percentage': 0.12}, {'relayer': 'edennetwork', 'percentage': 0.020689655172413793}, {'relayer': 'aestus', 'percentage': 0.04793103448275862}, {'relayer': 'bloxroute_regulated', 'percentage': 0.18586206896551724}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.19379310344827586}, {'relayer': 'flashbots', 'percentage': 0.18241379310344827}], 'nodeOperatorCount': 4, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Allnodes', 'aprPercentage': 4.48}, {'id': 'Staked.us', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 17645, 'avgCorrectness': 0.9839950250785927, 'avgInclusionDelay': 1.0832035078037132, 'avgUptime': 0.9851717800833385, 'avgValidatorEffectiveness': 91.88462421092014, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.1858641309675522}, {'client': 'Nimbus', 'percentage': 9.802960494069208e-05}, {'client': 'Prysm', 'percentage': 0.6133712381139104}, {'client': 'Teku', 'percentage': 0.2006666013135967}], 'networkPenetration': 0.02387453455143748, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.022641509433962263}, {'relayer': 'no_mev_boost', 'percentage': 0.007127882599580713}, {'relayer': 'ultra_sound_money', 'percentage': 0.2381551362683438}, {'relayer': 'blocknative', 'percentage': 0.1039832285115304}, {'relayer': 'edennetwork', 'percentage': 0.011320754716981131}, {'relayer': 'flashbots', 'percentage': 0.11614255765199162}, {'relayer': 'bloxroute_regulated', 'percentage': 0.06373165618448637}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.2020964360587002}, {'relayer': 'agnostic', 'percentage': 0.23438155136268343}, {'relayer': 'manifold', 'percentage': 0.0004192872117400419}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Staked.us', 'aprPercentage': 5.2}, {'id': 'Stakefish', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 16633, 'avgCorrectness': 0.994058180326725, 'avgInclusionDelay': 1.018652061823221, 'avgUptime': 0.9994040507917248, 'avgValidatorEffectiveness': 97.57234262980086, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.655275723246887}, {'client': 'Nimbus', 'percentage': 0.008613425709203258}, {'client': 'Prysm', 'percentage': 0.0006553693474393783}, {'client': 'Teku', 'percentage': 0.3354554816964704}], 'networkPenetration': 0.022263054208520955, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.16356673960612692}, {'relayer': 'edennetwork', 'percentage': 0.0032822757111597373}, {'relayer': 'flashbots', 'percentage': 0.10722100656455143}, {'relayer': 'ultra_sound_money', 'percentage': 0.17778993435448578}, {'relayer': 'blocknative', 'percentage': 0.06455142231947483}, {'relayer': 'bloxroute_regulated', 'percentage': 0.21936542669584244}, {'relayer': 'no_mev_boost', 'percentage': 0.01312910284463895}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.2510940919037199}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Stakefish', 'aprPercentage': 4.77}, + {'id': 'P2P.ORG - P2P Validator', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 13391, 'avgCorrectness': 0.9958846649369913, 'avgInclusionDelay': 1.0177862397361765, 'avgUptime': 0.9998770035723393, 'avgValidatorEffectiveness': 97.87750364727192, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.25857486394127327}, {'client': 'Nimbus', 'percentage': 0.014808252119984811}, {'client': 'Prysm', 'percentage': 0.20275914441209975}, {'client': 'Teku', 'percentage': 0.5238577395266422}], 'networkPenetration': 0.01811866773467267, 'relayerPercentages': [{'relayer': 'bloxroute_regulated', 'percentage': 0.20491803278688525}, {'relayer': 'ultra_sound_money', 'percentage': 0.1828499369482976}, {'relayer': 'agnostic', 'percentage': 0.23203026481715006}, {'relayer': 'flashbots', 'percentage': 0.08007566204287515}, {'relayer': 'edennetwork', 'percentage': 0.0018915510718789407}, {'relayer': 'aestus', 'percentage': 0.03972257250945776}, {'relayer': 'blocknative', 'percentage': 0.02395964691046658}, {'relayer': 'no_mev_boost', 'percentage': 0.0025220680958385876}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.22887767969735182}, {'relayer': 'manifold', 'percentage': 0.0031525851197982345}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'P2P.ORG - P2P Validator', 'aprPercentage': 4.9}, {'id': 'InfStones', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 10040, 'avgCorrectness': 0.9946845322821294, 'avgInclusionDelay': 1.0169943927890641, 'avgUptime': 0.9995934803185333, 'avgValidatorEffectiveness': 97.80254372105335, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0012313104661389623}, {'client': 'Prysm', 'percentage': 0.9978891820580474}, {'client': 'Teku', 'percentage': 0.0008795074758135445}], 'networkPenetration': 0.013584603394527191, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.0858806404657933}, {'relayer': 'no_mev_boost', 'percentage': 0.08151382823871907}, {'relayer': 'agnostic', 'percentage': 0.15211062590975255}, {'relayer': 'bloxroute_regulated', 'percentage': 0.18922852983988356}, {'relayer': 'edennetwork', 'percentage': 0.011644832605531296}, {'relayer': 'blocknative', 'percentage': 0.01455604075691412}, {'relayer': 'aestus', 'percentage': 0.029839883551673944}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.23871906841339155}, {'relayer': 'manifold', 'percentage': 0.002911208151382824}, {'relayer': 'ultra_sound_money', 'percentage': 0.19359534206695778}], 'nodeOperatorCount': 3, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'InfStones', 'aprPercentage': 4.31}, {'id': 'Chorus One', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 9133, 'avgCorrectness': 0.9957167960152612, 'avgInclusionDelay': 1.0181790652773917, 'avgUptime': 0.9996925146456918, 'avgValidatorEffectiveness': 97.80409987138766, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.999198717948718}, {'client': 'Teku', 'percentage': 0.0008012820512820513}], 'networkPenetration': 0.012357388725320401, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.17647058823529413}, {'relayer': 'blocknative', 'percentage': 0.1607843137254902}, {'relayer': 'agnostic', 'percentage': 0.2679738562091503}, {'relayer': 'edennetwork', 'percentage': 0.013071895424836602}, {'relayer': 'ultra_sound_money', 'percentage': 0.37516339869281046}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.00522875816993464}, {'relayer': 'no_mev_boost', 'percentage': 0.00130718954248366}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Chorus One', 'aprPercentage': 4.34}, {'id': 'CryptoManufaktur', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 9089, 'avgCorrectness': 0.9927518070477906, 'avgInclusionDelay': 1.0228105817011528, 'avgUptime': 0.9980663214827067, 'avgValidatorEffectiveness': 96.95081621430828, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.23091020158827122}, {'client': 'Nimbus', 'percentage': 0.06760333944206882}, {'client': 'Prysm', 'percentage': 0.003868865811443698}, {'client': 'Teku', 'percentage': 0.6976175931582163}], 'networkPenetration': 0.01229785460685833, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.006793478260869565}, {'relayer': 'no_mev_boost', 'percentage': 0.010869565217391304}, {'relayer': 'ultra_sound_money', 'percentage': 0.25679347826086957}, {'relayer': 'flashbots', 'percentage': 0.13043478260869565}, {'relayer': 'agnostic', 'percentage': 0.012228260869565218}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.4578804347826087}, {'relayer': 'aestus', 'percentage': 0.010869565217391304}, {'relayer': 'blocknative', 'percentage': 0.11413043478260869}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'CryptoManufaktur', 'aprPercentage': 4.63}, {'id': 'RockX', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8906, 'avgCorrectness': 0.9958600589508909, 'avgInclusionDelay': 1.0195199858326254, 'avgUptime': 0.9997379603236106, 'avgValidatorEffectiveness': 97.70340844311204, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.045201115093588214}, {'client': 'Nimbus', 'percentage': 0.00019912385503783353}, {'client': 'Prysm', 'percentage': 0.9542015133412983}, {'client': 'Teku', 'percentage': 0.00039824771007566706}], 'networkPenetration': 0.012050246795981987, 'relayerPercentages': [{'relayer': 'blocknative', 'percentage': 0.3310734463276836}, {'relayer': 'edennetwork', 'percentage': 0.05649717514124294}, {'relayer': 'ultra_sound_money', 'percentage': 0.3581920903954802}, {'relayer': 'flashbots', 'percentage': 0.2519774011299435}, {'relayer': 'no_mev_boost', 'percentage': 0.0022598870056497176}], 'nodeOperatorCount': 3, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'RockX', 'aprPercentage': 5.67}, {'id': 'Stakely', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8841, 'avgCorrectness': 0.996062090365428, 'avgInclusionDelay': 1.0170162257276796, 'avgUptime': 0.9995074629298876, 'avgValidatorEffectiveness': 97.92693458535189, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.380672268907563}, {'client': 'Nimbus', 'percentage': 0.026050420168067228}, {'client': 'Prysm', 'percentage': 0.06281512605042017}, {'client': 'Teku', 'percentage': 0.5304621848739496}], 'networkPenetration': 0.011962298666435747, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.047830923248053395}, {'relayer': 'manifold', 'percentage': 0.010567296996662959}, {'relayer': 'bloxroute_regulated', 'percentage': 0.19688542825361513}, {'relayer': 'ultra_sound_money', 'percentage': 0.15739710789766406}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.20022246941045607}, {'relayer': 'flashbots', 'percentage': 0.10789766407119021}, {'relayer': 'blocknative', 'percentage': 0.10734149054505006}, {'relayer': 'edennetwork', 'percentage': 0.02335928809788654}, {'relayer': 'agnostic', 'percentage': 0.14794215795328142}, {'relayer': 'no_mev_boost', 'percentage': 0.0005561735261401557}], 'nodeOperatorCount': 3, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Stakely', 'aprPercentage': 4.49}, {'id': 'Blockscape', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8837, 'avgCorrectness': 0.9935475539934409, 'avgInclusionDelay': 1.0208816215532739, 'avgUptime': 0.9977106291385561, 'avgValidatorEffectiveness': 97.15939097807697, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.3155885897950984}, {'client': 'Nimbus', 'percentage': 0.004017677782241864}, {'client': 'Prysm', 'percentage': 0.6287665729208517}, {'client': 'Teku', 'percentage': 0.051627159501807955}], 'networkPenetration': 0.011956886473848285, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.18379446640316205}, {'relayer': 'no_mev_boost', 'percentage': 0.004940711462450593}, {'relayer': 'aestus', 'percentage': 0.04743083003952569}, {'relayer': 'flashbots', 'percentage': 0.09782608695652174}, {'relayer': 'blocknative', 'percentage': 0.07114624505928854}, {'relayer': 'bloxroute_regulated', 'percentage': 0.17588932806324112}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.20355731225296442}, {'relayer': 'ultra_sound_money', 'percentage': 0.21541501976284586}], 'nodeOperatorCount': 3, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Blockscape', 'aprPercentage': 4.67}, {'id': 'HashQuark', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8836, 'avgCorrectness': 0.9949206389618414, 'avgInclusionDelay': 1.017041544654457, 'avgUptime': 0.9998608457106367, 'avgValidatorEffectiveness': 97.8491555981025, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0025375343624444912}, {'client': 'Prysm', 'percentage': 0.9953478536688518}, {'client': 'Teku', 'percentage': 0.002114611968703743}], 'networkPenetration': 0.01195553342570142, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.18934081346423562}, {'relayer': 'ultra_sound_money', 'percentage': 0.20757363253856942}, {'relayer': 'blocknative', 'percentage': 0.14446002805049088}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.28190743338008417}, {'relayer': 'flashbots', 'percentage': 0.1458625525946704}, {'relayer': 'edennetwork', 'percentage': 0.024544179523141654}, {'relayer': 'no_mev_boost', 'percentage': 0.006311360448807854}], 'nodeOperatorCount': 3, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'HashQuark', 'aprPercentage': 4.89}, {'id': 'DSRV', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8836, 'avgCorrectness': 0.9928789252057733, 'avgInclusionDelay': 1.0239355710374545, 'avgUptime': 0.9989957431346593, 'avgValidatorEffectiveness': 96.93890430703671, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.9058072241069647}, {'client': 'Nimbus', 'percentage': 0.09419277589303532}], 'networkPenetration': 0.01195553342570142, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.0010787486515641855}, {'relayer': 'manifold', 'percentage': 0.0010787486515641855}, {'relayer': 'aestus', 'percentage': 0.02696871628910464}, {'relayer': 'flashbots', 'percentage': 0.0825242718446602}, {'relayer': 'blocknative', 'percentage': 0.07281553398058252}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.20658036677454153}, {'relayer': 'agnostic', 'percentage': 0.21251348435814454}, {'relayer': 'ultra_sound_money', 'percentage': 0.18878101402373246}, {'relayer': 'edennetwork', 'percentage': 0.012944983818770227}, {'relayer': 'bloxroute_regulated', 'percentage': 0.1947141316073355}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'DSRV', 'aprPercentage': 4.57}, {'id': 'Figment', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8708, 'avgCorrectness': 0.9955740206679393, 'avgInclusionDelay': 1.0190380857408192, 'avgUptime': 0.9996670966805541, 'avgValidatorEffectiveness': 97.71107309210294, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.929744335896903}, {'client': 'Nimbus', 'percentage': 0.0031178549158179174}, {'client': 'Teku', 'percentage': 0.06713780918727916}], 'networkPenetration': 0.011782343262902667, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.002797202797202797}, {'relayer': 'flashbots', 'percentage': 0.4251748251748252}, {'relayer': 'blocknative', 'percentage': 0.5720279720279721}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Figment', 'aprPercentage': 4.71}, + {'id': 'Stakin', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8689, 'avgCorrectness': 0.9938672503576078, 'avgInclusionDelay': 1.0189147178705689, 'avgUptime': 0.9997436848590799, 'avgValidatorEffectiveness': 97.56509803881151, 'clientPercentages': [{'client': 'Nimbus', 'percentage': 0.022667476219388787}, {'client': 'Prysm', 'percentage': 0.0010119409026512851}, {'client': 'Teku', 'percentage': 0.97632058287796}], 'networkPenetration': 0.011756635348112228, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.016337644656228726}, {'relayer': 'agnostic', 'percentage': 0.33900612661674606}, {'relayer': 'manifold', 'percentage': 0.004765146358066712}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.055820285908781485}, {'relayer': 'bloxroute_regulated', 'percentage': 0.049693669162695714}, {'relayer': 'no_mev_boost', 'percentage': 0.004765146358066712}, {'relayer': 'ultra_sound_money', 'percentage': 0.3281143635125936}, {'relayer': 'flashbots', 'percentage': 0.1415929203539823}, {'relayer': 'blocknative', 'percentage': 0.02859087814840027}, {'relayer': 'aestus', 'percentage': 0.031313818924438394}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Stakin', 'aprPercentage': 5.32}, {'id': 'Everstake', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8689, 'avgCorrectness': 0.9943589885707704, 'avgInclusionDelay': 1.0211055372217654, 'avgUptime': 0.9991016087154804, 'avgValidatorEffectiveness': 97.35801508939767, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0014733740265207324}, {'client': 'Nimbus', 'percentage': 0.0008419280151547042}, {'client': 'Prysm', 'percentage': 0.9960008419280152}, {'client': 'Teku', 'percentage': 0.0016838560303094085}], 'networkPenetration': 0.011756635348112228, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.018488308863512777}, {'relayer': 'ultra_sound_money', 'percentage': 0.1631321370309951}, {'relayer': 'flashbots', 'percentage': 0.10114192495921696}, {'relayer': 'no_mev_boost', 'percentage': 0.002175095160413268}, {'relayer': 'agnostic', 'percentage': 0.15551930396954866}, {'relayer': 'manifold', 'percentage': 0.0016313213703099511}, {'relayer': 'aestus', 'percentage': 0.03588907014681892}, {'relayer': 'bloxroute_regulated', 'percentage': 0.20010875475802067}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.21642196846112016}, {'relayer': 'blocknative', 'percentage': 0.1054921152800435}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Everstake', 'aprPercentage': 5.55}, {'id': 'Simply Staking', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8689, 'avgCorrectness': 0.9912519619610377, 'avgInclusionDelay': 1.0239505183746773, 'avgUptime': 0.9982426651019577, 'avgValidatorEffectiveness': 96.71023913193015, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.3016747933008268}, {'client': 'Nimbus', 'percentage': 0.17129531481874072}, {'client': 'Prysm', 'percentage': 0.5185499258002968}, {'client': 'Teku', 'percentage': 0.00847996608013568}], 'networkPenetration': 0.011756635348112228, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.004243281471004243}, {'relayer': 'aestus', 'percentage': 0.04031117397454031}, {'relayer': 'edennetwork', 'percentage': 0.01272984441301273}, {'relayer': 'bloxroute_regulated', 'percentage': 0.17114568599717114}, {'relayer': 'blocknative', 'percentage': 0.11032531824611033}, {'relayer': 'flashbots', 'percentage': 0.11032531824611033}, {'relayer': 'manifold', 'percentage': 0.0021216407355021216}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.185997171145686}, {'relayer': 'agnostic', 'percentage': 0.18175388967468176}, {'relayer': 'ultra_sound_money', 'percentage': 0.18104667609618105}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Simply Staking', 'aprPercentage': 4.94}, {'id': 'Attestant', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8688, 'avgCorrectness': 0.99618261364101, 'avgInclusionDelay': 1.0163649001773056, 'avgUptime': 0.9999527998560199, 'avgValidatorEffectiveness': 98.0437402422617, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.009888491479065854}, {'client': 'Nimbus', 'percentage': 0.16705238796549549}, {'client': 'Prysm', 'percentage': 0.3208499894803282}, {'client': 'Teku', 'percentage': 0.5022091310751104}], 'networkPenetration': 0.011755282299965362, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.08050436469447139}, {'relayer': 'no_mev_boost', 'percentage': 0.0038797284190106693}, {'relayer': 'blocknative', 'percentage': 0.06789524733268672}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.3006789524733269}, {'relayer': 'bloxroute_regulated', 'percentage': 0.27740058195926287}, {'relayer': 'aestus', 'percentage': 0.03685741998060136}, {'relayer': 'agnostic', 'percentage': 0.12027158098933075}, {'relayer': 'ultra_sound_money', 'percentage': 0.11251212415130941}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Attestant', 'aprPercentage': 4.88}, {'id': 'Kukis Global', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8688, 'avgCorrectness': 0.9960943052296969, 'avgInclusionDelay': 1.0166570024175492, 'avgUptime': 0.9999093270979418, 'avgValidatorEffectiveness': 98.00276580621082, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.9851190476190477}, {'client': 'Nimbus', 'percentage': 0.001488095238095238}, {'client': 'Prysm', 'percentage': 0.013392857142857142}], 'networkPenetration': 0.011755282299965362, 'relayerPercentages': [{'relayer': 'ultra_sound_money', 'percentage': 0.24265644955300128}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.18646232439335889}, {'relayer': 'agnostic', 'percentage': 0.19923371647509577}, {'relayer': 'flashbots', 'percentage': 0.14431673052362706}, {'relayer': 'manifold', 'percentage': 0.005108556832694764}, {'relayer': 'edennetwork', 'percentage': 0.005108556832694764}, {'relayer': 'no_mev_boost', 'percentage': 0.005108556832694764}, {'relayer': 'aestus', 'percentage': 0.09323116219667944}, {'relayer': 'blocknative', 'percentage': 0.11877394636015326}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Kukis Global', 'aprPercentage': 4.6}, {'id': 'Prysmatic Labs', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8688, 'avgCorrectness': 0.9954936038211813, 'avgInclusionDelay': 1.016571774235335, 'avgUptime': 0.9999312501991058, 'avgValidatorEffectiveness': 97.95401079239907, 'clientPercentages': [{'client': 'Prysm', 'percentage': 1.0}], 'networkPenetration': 0.011755282299965362, 'relayerPercentages': [{'relayer': 'ultra_sound_money', 'percentage': 0.4992548435171386}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.4932935916542474}, {'relayer': 'no_mev_boost', 'percentage': 0.004470938897168405}, {'relayer': 'manifold', 'percentage': 0.0029806259314456036}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Prysmatic Labs', 'aprPercentage': 4.71}, {'id': 'Nethermind', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8688, 'avgCorrectness': 0.9948590182915753, 'avgInclusionDelay': 1.0177241936671055, 'avgUptime': 0.9998144081354832, 'avgValidatorEffectiveness': 97.77641298267793, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.31564305585277125}, {'client': 'Nimbus', 'percentage': 0.00042799058420714745}, {'client': 'Prysm', 'percentage': 0.6832869676867109}, {'client': 'Teku', 'percentage': 0.0006419858763107211}], 'networkPenetration': 0.011755282299965362, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.12187088274044795}, {'relayer': 'aestus', 'percentage': 0.021080368906455864}, {'relayer': 'no_mev_boost', 'percentage': 0.002635046113306983}, {'relayer': 'ultra_sound_money', 'percentage': 0.17654808959156784}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.24703557312252963}, {'relayer': 'bloxroute_regulated', 'percentage': 0.2450592885375494}, {'relayer': 'edennetwork', 'percentage': 0.00922266139657444}, {'relayer': 'flashbots', 'percentage': 0.08168642951251646}, {'relayer': 'blocknative', 'percentage': 0.09486166007905138}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Nethermind', 'aprPercentage': 4.39}, {'id': 'Blockdaemon', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8600, 'avgCorrectness': 0.9963515958711777, 'avgInclusionDelay': 1.0189893828373544, 'avgUptime': 0.9996228439747284, 'avgValidatorEffectiveness': 97.79138247996755, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0006491398896462187}, {'client': 'Nimbus', 'percentage': 0.0006491398896462187}, {'client': 'Prysm', 'percentage': 0.9974034404414152}, {'client': 'Teku', 'percentage': 0.0012982797792924375}], 'networkPenetration': 0.011636214063041219, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.3770491803278688}, {'relayer': 'blocknative', 'percentage': 0.03278688524590164}, {'relayer': 'no_mev_boost', 'percentage': 0.006557377049180328}, {'relayer': 'edennetwork', 'percentage': 0.00819672131147541}, {'relayer': 'bloxroute_regulated', 'percentage': 0.5754098360655737}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Blockdaemon', 'aprPercentage': 4.53}, {'id': 'ChainLayer', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8600, 'avgCorrectness': 0.9948749220390432, 'avgInclusionDelay': 1.0180623615724071, 'avgUptime': 0.9998356867176349, 'avgValidatorEffectiveness': 97.74835621844464, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.7317171717171718}, {'client': 'Nimbus', 'percentage': 0.0006060606060606061}, {'client': 'Teku', 'percentage': 0.2676767676767677}], 'networkPenetration': 0.011636214063041219, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.0171606864274571}, {'relayer': 'ultra_sound_money', 'percentage': 0.5288611544461779}, {'relayer': 'blocknative', 'percentage': 0.2917316692667707}, {'relayer': 'flashbots', 'percentage': 0.1622464898595944}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'ChainLayer', 'aprPercentage': 4.45}, {'id': 'Consensys', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8563, 'avgCorrectness': 0.9944057551443066, 'avgInclusionDelay': 1.0217016505729477, 'avgUptime': 0.999439080902239, 'avgValidatorEffectiveness': 97.33514498765409, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.5072649572649572}, {'client': 'Teku', 'percentage': 0.49273504273504276}], 'networkPenetration': 0.011586151281607204, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.009458297506448839}, {'relayer': 'manifold', 'percentage': 0.0034393809114359416}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.25365434221840066}, {'relayer': 'agnostic', 'percentage': 0.13241616509028376}, {'relayer': 'ultra_sound_money', 'percentage': 0.21066208082545143}, {'relayer': 'aestus', 'percentage': 0.018056749785038694}, {'relayer': 'blocknative', 'percentage': 0.07738607050730868}, {'relayer': 'bloxroute_regulated', 'percentage': 0.20808254514187446}, {'relayer': 'flashbots', 'percentage': 0.08684436801375753}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Consensys', 'aprPercentage': 4.87}, + {'id': 'Staking Facilities', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8400, 'avgCorrectness': 0.994360242674693, 'avgInclusionDelay': 1.0182986656460584, 'avgUptime': 0.9997052910052909, 'avgValidatorEffectiveness': 97.66347866764276, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.5939516129032258}, {'client': 'Nimbus', 'percentage': 0.31794354838709676}, {'client': 'Prysm', 'percentage': 0.04112903225806452}, {'client': 'Teku', 'percentage': 0.0469758064516129}], 'networkPenetration': 0.011365604433668167, 'relayerPercentages': [{'relayer': 'blocknative', 'percentage': 0.06458333333333334}, {'relayer': 'manifold', 'percentage': 0.004166666666666667}, {'relayer': 'bloxroute_regulated', 'percentage': 0.24375}, {'relayer': 'aestus', 'percentage': 0.027083333333333334}, {'relayer': 'ultra_sound_money', 'percentage': 0.13958333333333334}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.284375}, {'relayer': 'no_mev_boost', 'percentage': 0.00625}, {'relayer': 'flashbots', 'percentage': 0.08333333333333333}, {'relayer': 'agnostic', 'percentage': 0.14583333333333334}, {'relayer': 'edennetwork', 'percentage': 0.0010416666666666667}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Staking Facilities', 'aprPercentage': 7.99}, {'id': 'BridgeTower', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8150, 'avgCorrectness': 0.9924621172569185, 'avgInclusionDelay': 1.0243753303982621, 'avgUptime': 0.9990097575226409, 'avgValidatorEffectiveness': 96.86495904049782, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.011027342396951852, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.3492753623188406}, {'relayer': 'flashbots', 'percentage': 0.06521739130434782}, {'relayer': 'no_mev_boost', 'percentage': 0.04057971014492753}, {'relayer': 'manifold', 'percentage': 0.014492753623188406}, {'relayer': 'ultra_sound_money', 'percentage': 0.4115942028985507}, {'relayer': 'bloxroute_ethical', 'percentage': 0.004347826086956522}, {'relayer': 'agnostic', 'percentage': 0.1144927536231884}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'BridgeTower', 'aprPercentage': 5.06}, {'id': 'Twinstake', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 8004, 'avgCorrectness': 0.9949416039210882, 'avgInclusionDelay': 1.017254663782941, 'avgUptime': 0.9997087170700364, 'avgValidatorEffectiveness': 97.81507111435947, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.41018329938900205}, {'client': 'Nimbus', 'percentage': 0.0006109979633401223}, {'client': 'Prysm', 'percentage': 0.5885947046843177}, {'client': 'Teku', 'percentage': 0.0006109979633401223}], 'networkPenetration': 0.010829797367509525, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.22995031937544358}, {'relayer': 'aestus', 'percentage': 0.0269694819020582}, {'relayer': 'blocknative', 'percentage': 0.10432931156848829}, {'relayer': 'bloxroute_regulated', 'percentage': 0.22782114975159687}, {'relayer': 'flashbots', 'percentage': 0.099361249112846}, {'relayer': 'no_mev_boost', 'percentage': 0.0021291696238466998}, {'relayer': 'agnostic', 'percentage': 0.12207239176721078}, {'relayer': 'ultra_sound_money', 'percentage': 0.18594748048261178}, {'relayer': 'manifold', 'percentage': 0.0014194464158978}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Twinstake', 'aprPercentage': 4.67}, {'id': 'RockLogic GmbH', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 5789, 'avgCorrectness': 0.9669165677145917, 'avgInclusionDelay': 1.0789132648338424, 'avgUptime': 0.9905275193511506, 'avgValidatorEffectiveness': 89.24502097730063, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.24512228634240177}, {'client': 'Nimbus', 'percentage': 0.2044517724649629}, {'client': 'Prysm', 'percentage': 0.13190436933223412}, {'client': 'Teku', 'percentage': 0.41852157186040123}], 'networkPenetration': 0.007832795722202978, 'relayerPercentages': [{'relayer': 'edennetwork', 'percentage': 0.01800450112528132}, {'relayer': 'ultra_sound_money', 'percentage': 0.14103525881470366}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.22580645161290322}, {'relayer': 'blocknative', 'percentage': 0.11477869467366841}, {'relayer': 'manifold', 'percentage': 0.003000750187546887}, {'relayer': 'agnostic', 'percentage': 0.1447861965491373}, {'relayer': 'flashbots', 'percentage': 0.09377344336084022}, {'relayer': 'aestus', 'percentage': 0.0450112528132033}, {'relayer': 'bloxroute_regulated', 'percentage': 0.21380345086271568}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'RockLogic GmbH', 'aprPercentage': 4.63}, {'id': 'Bloxstaking', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 3603, 'avgCorrectness': 0.9945387425509173, 'avgInclusionDelay': 1.0176651243832087, 'avgUptime': 0.9978023717654465, 'avgValidatorEffectiveness': 97.55305862255113, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.4220935841775205}, {'client': 'Lodestar', 'percentage': 0.000964785335262904}, {'client': 'Nimbus', 'percentage': 0.001447178002894356}, {'client': 'Prysm', 'percentage': 0.5735648818137964}, {'client': 'Teku', 'percentage': 0.001929570670525808}], 'networkPenetration': 0.004875032473155525, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.005063291139240506}, {'relayer': 'ultra_sound_money', 'percentage': 0.3468354430379747}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.42278481012658226}, {'relayer': 'no_mev_boost', 'percentage': 0.007594936708860759}, {'relayer': 'flashbots', 'percentage': 0.18227848101265823}, {'relayer': 'agnostic', 'percentage': 0.035443037974683546}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Bloxstaking', 'aprPercentage': 4.88}, {'id': 'StakeWise Labs', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 1549, 'avgCorrectness': 0.9911297886964764, 'avgInclusionDelay': 1.025921921892341, 'avgUptime': 0.9986707245842171, 'avgValidatorEffectiveness': 96.63512114509618, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.49390243902439024}, {'client': 'Nimbus', 'percentage': 0.012195121951219513}, {'client': 'Teku', 'percentage': 0.49390243902439024}], 'networkPenetration': 0.0020958715794942846, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.17901234567901234}, {'relayer': 'agnostic', 'percentage': 0.32098765432098764}, {'relayer': 'blocknative', 'percentage': 0.16666666666666666}, {'relayer': 'ultra_sound_money', 'percentage': 0.3271604938271605}, {'relayer': 'no_mev_boost', 'percentage': 0.006172839506172839}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'StakeWise Labs', 'aprPercentage': 4.76}, {'id': 'XHash', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 1335, 'avgCorrectness': 0.9959948770573308, 'avgInclusionDelay': 1.017659356132019, 'avgUptime': 0.999878063169219, 'avgValidatorEffectiveness': 97.90271166751971, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.001034126163391934}, {'client': 'Prysm', 'percentage': 0.9989658738366081}], 'networkPenetration': 0.0018063192760651196, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.1501416430594901}, {'relayer': 'bloxroute_regulated', 'percentage': 0.21813031161473087}, {'relayer': 'aestus', 'percentage': 0.039660056657223795}, {'relayer': 'edennetwork', 'percentage': 0.028328611898016998}, {'relayer': 'ultra_sound_money', 'percentage': 0.1558073654390935}, {'relayer': 'blocknative', 'percentage': 0.08498583569405099}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.2237960339943343}, {'relayer': 'flashbots', 'percentage': 0.09348441926345609}, {'relayer': 'manifold', 'percentage': 0.0056657223796034}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'XHash', 'aprPercentage': 4.62}, {'id': 'Jump Crypto', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 1000, 'avgCorrectness': 0.9936811827711054, 'avgInclusionDelay': 1.0219393434207382, 'avgUptime': 0.9993498412698412, 'avgValidatorEffectiveness': 97.23858990271765, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.8631415241057543}, {'client': 'Nimbus', 'percentage': 0.0015552099533437014}, {'client': 'Prysm', 'percentage': 0.13530326594090203}], 'networkPenetration': 0.0013530481468652582, 'relayerPercentages': [{'relayer': 'bloxroute_regulated', 'percentage': 0.7571428571428571}, {'relayer': 'flashbots', 'percentage': 0.24285714285714285}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Jump Crypto', 'aprPercentage': 4.6}, {'id': 'Ebunker', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 524, 'avgCorrectness': 0.9934322392503543, 'avgInclusionDelay': 1.0211397632375883, 'avgUptime': 0.999540470947034, 'avgValidatorEffectiveness': 97.30198179435276, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.1962864721485411}, {'client': 'Prysm', 'percentage': 0.8010610079575596}, {'client': 'Teku', 'percentage': 0.002652519893899204}], 'networkPenetration': 0.0007089972289573952, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 0.1323529411764706}, {'relayer': 'ultra_sound_money', 'percentage': 0.35294117647058826}, {'relayer': 'agnostic', 'percentage': 0.4117647058823529}, {'relayer': 'blocknative', 'percentage': 0.10294117647058823}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Ebunker', 'aprPercentage': 5.45}, {'id': 'Finoa', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 250, 'avgCorrectness': 0.9925403531413252, 'avgInclusionDelay': 1.0201738184940643, 'avgUptime': 0.9996926984126984, 'avgValidatorEffectiveness': 97.30700362564548, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.12962962962962962}, {'client': 'Nimbus', 'percentage': 0.024691358024691357}, {'client': 'Teku', 'percentage': 0.845679012345679}], 'networkPenetration': 0.00033826203671631454, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Finoa', 'aprPercentage': 3.53}, {'id': 'snc.xyz', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 221, 'avgCorrectness': 0.9935630609797361, 'avgInclusionDelay': 1.0216443325905327, 'avgUptime': 0.9996315192716692, 'avgValidatorEffectiveness': 97.30257516396266, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.000299023640457222, 'relayerPercentages': [{'relayer': 'ultra_sound_money', 'percentage': 0.9411764705882353}, {'relayer': 'no_mev_boost', 'percentage': 0.058823529411764705}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'snc.xyz', 'aprPercentage': 4.23}, + {'id': 'SenseiNode', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 192, 'avgCorrectness': 0.9809404595495259, 'avgInclusionDelay': 1.0506775040756493, 'avgUptime': 0.9959623015873016, 'avgValidatorEffectiveness': 93.19754426398607, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.00025978524419812955, 'relayerPercentages': [{'relayer': 'ultra_sound_money', 'percentage': 0.08695652173913043}, {'relayer': 'blocknative', 'percentage': 0.21739130434782608}, {'relayer': 'bloxroute_regulated', 'percentage': 0.13043478260869565}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.13043478260869565}, {'relayer': 'flashbots', 'percentage': 0.30434782608695654}, {'relayer': 'edennetwork', 'percentage': 0.13043478260869565}], 'nodeOperatorCount': 2, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'SenseiNode', 'aprPercentage': 3.9}, {'id': 'VeriHash', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 191, 'avgCorrectness': 0.9922467964309275, 'avgInclusionDelay': 1.0247556091612413, 'avgUptime': 0.9977029834621457, 'avgValidatorEffectiveness': 96.68154167659868, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.6060606060606061}, {'client': 'Teku', 'percentage': 0.3939393939393939}], 'networkPenetration': 0.00025843219605126426, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 0.1}, {'relayer': 'ultra_sound_money', 'percentage': 0.05}, {'relayer': 'agnostic', 'percentage': 0.05}, {'relayer': 'aestus', 'percentage': 0.05}, {'relayer': 'blocknative', 'percentage': 0.15}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.25}, {'relayer': 'flashbots', 'percentage': 0.35}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'VeriHash', 'aprPercentage': 3.96}, {'id': 'Prysm Client Team', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.9955700184019143, 'avgInclusionDelay': 1.0158429899289203, 'avgUptime': 0.99994708994709, 'avgValidatorEffectiveness': 98.0326744163054, 'clientPercentages': [{'client': 'Prysm', 'percentage': 1.0}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}, {'name': 'client_team', 'path': None, 'idType': None}], 'displayName': 'Prysm Client Team', 'aprPercentage': 3.56}, {'id': 'Lighthouse Client Team', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.9946959974479633, 'avgInclusionDelay': 1.0180421350668365, 'avgUptime': 0.9997663139329805, 'avgValidatorEffectiveness': 97.73502956643297, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}, {'name': 'client_team', 'path': None, 'idType': None}], 'displayName': 'Lighthouse Client Team', 'aprPercentage': 4.01}, {'id': 'Teku Client Team', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.9924418878223226, 'avgInclusionDelay': 1.0207818675889329, 'avgUptime': 0.9995061728395063, 'avgValidatorEffectiveness': 97.23308858857054, 'clientPercentages': [{'client': 'Teku', 'percentage': 1.0}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}, {'name': 'client_team', 'path': None, 'idType': None}], 'displayName': 'Teku Client Team', 'aprPercentage': 3.24}, {'id': 'Nimbus Client Team', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.9929111044436583, 'avgInclusionDelay': 1.0222697900200173, 'avgUptime': 0.9845987654320987, 'avgValidatorEffectiveness': 95.69371655780313, 'clientPercentages': [{'client': 'Nimbus', 'percentage': 1.0}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Nimbus Client Team', 'aprPercentage': 3.38}, {'id': 'T-Systems', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.9906790454138277, 'avgInclusionDelay': 1.0409038787626566, 'avgUptime': 0.9959038800705469, 'avgValidatorEffectiveness': 94.9495423230297, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'aestus', 'percentage': 0.16666666666666666}, {'relayer': 'agnostic', 'percentage': 0.16666666666666666}, {'relayer': 'ultra_sound_money', 'percentage': 0.3333333333333333}, {'relayer': 'bloxroute_maxprofit', 'percentage': 0.3333333333333333}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'T-Systems', 'aprPercentage': 3.49}, {'id': 'Erigon Client Team', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 144, 'avgCorrectness': 0.946656980437704, 'avgInclusionDelay': 1.140015336529191, 'avgUptime': 0.9832319223985889, 'avgValidatorEffectiveness': 82.04258062687084, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.8888888888888888}, {'client': 'Nimbus', 'percentage': 0.013888888888888888}, {'client': 'Prysm', 'percentage': 0.09722222222222222}], 'networkPenetration': 0.00019483893314859715, 'relayerPercentages': [{'relayer': 'no_mev_boost', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Erigon Client Team', 'aprPercentage': 3.28}, {'id': 'Gateway.fm', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 105, 'avgCorrectness': 0.9866219046868783, 'avgInclusionDelay': 1.0368256161337042, 'avgUptime': 0.9978594104308391, 'avgValidatorEffectiveness': 95.08060158443801, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 0.0001420700554208521, 'relayerPercentages': [{'relayer': 'bloxroute_regulated', 'percentage': 0.75}, {'relayer': 'blocknative', 'percentage': 0.25}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Gateway.fm', 'aprPercentage': 3.73}, {'id': 'Northstake', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 69, 'avgCorrectness': 0.9951694386278869, 'avgInclusionDelay': 1.0184213196564798, 'avgUptime': 0.9999263860133425, 'avgValidatorEffectiveness': 97.74554961425568, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 9.336032213370281e-05, 'relayerPercentages': [{'relayer': 'bloxroute_maxprofit', 'percentage': 0.125}, {'relayer': 'ultra_sound_money', 'percentage': 0.125}, {'relayer': 'blocknative', 'percentage': 0.25}, {'relayer': 'flashbots', 'percentage': 0.125}, {'relayer': 'bloxroute_regulated', 'percentage': 0.375}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Northstake', 'aprPercentage': 4.91}, {'id': 'Coinbase Cloud', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 26, 'avgCorrectness': 0.9960031584002865, 'avgInclusionDelay': 1.0152384673618404, 'avgUptime': 0.99997557997558, 'avgValidatorEffectiveness': 98.1332785342785, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 3.517925181849671e-05, 'relayerPercentages': [{'relayer': 'agnostic', 'percentage': 0.5}, {'relayer': 'ultra_sound_money', 'percentage': 0.5}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Coinbase Cloud', 'aprPercentage': 3.68}, {'id': 'PierTwo', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 23, 'avgCorrectness': 0.9930432499835926, 'avgInclusionDelay': 1.0221640930441875, 'avgUptime': 0.9997791580400276, 'avgValidatorEffectiveness': 97.19243749696746, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 1.0}], 'networkPenetration': 2.841401108417042e-05, 'relayerPercentages': [], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'PierTwo', 'aprPercentage': 2.84}, {'id': 'Anyblock Analytics', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 13, 'avgCorrectness': 0.996206015013108, 'avgInclusionDelay': 1.0191490401055152, 'avgUptime': 0.9998046398046397, 'avgValidatorEffectiveness': 97.77196687997578, 'clientPercentages': [{'client': 'Lighthouse', 'percentage': 0.0034904013961605585}, {'client': 'Nimbus', 'percentage': 0.0017452006980802793}, {'client': 'Prysm', 'percentage': 0.9912739965095986}, {'client': 'Teku', 'percentage': 0.0034904013961605585}], 'networkPenetration': 1.7589625909248354e-05, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Anyblock Analytics', 'aprPercentage': 4.71}, {'id': 'Blockshard', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 4, 'avgCorrectness': 0.9959246321583571, 'avgInclusionDelay': 1.0168307399174341, 'avgUptime': 0.9996825396825396, 'avgValidatorEffectiveness': 97.98058899384324, 'clientPercentages': [{'client': 'Nimbus', 'percentage': 1.0}], 'networkPenetration': 5.412192587461032e-06, 'relayerPercentages': [{'relayer': 'flashbots', 'percentage': 1.0}], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Blockshard', 'aprPercentage': 7.92}, {'id': 'Chainnodes', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 3, 'avgCorrectness': 0.9914529914529915, 'avgInclusionDelay': 1.0273363000635727, 'avgUptime': 0.9987301587301587, 'avgValidatorEffectiveness': 96.42456435794274, 'clientPercentages': [{'client': 'Unknown', 'percentage': 1.0}], 'networkPenetration': 4.059144440595774e-06, 'relayerPercentages': [], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Chainnodes', 'aprPercentage': 2.84}, {'id': 'Brick Towers', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 2, 'avgCorrectness': 0.9879250079440737, 'avgInclusionDelay': 1.0241499841118527, 'avgUptime': 0.9990476190476191, 'avgValidatorEffectiveness': 96.38715286960364, 'clientPercentages': [{'client': 'Unknown', 'percentage': 1.0}], 'networkPenetration': 2.706096293730516e-06, 'relayerPercentages': [], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'selfReport', 'path': None, 'idType': None}, {'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Brick Towers', 'aprPercentage': 2.83}, {'id': 'Audit.one', 'idType': 'nodeOperator', 'timeWindow': '7d', 'validatorCount': 1, 'avgCorrectness': 0.9955555555555555, 'avgInclusionDelay': 1.0133333333333334, 'avgUptime': 1.0, 'avgValidatorEffectiveness': 98.27150991554119, 'clientPercentages': [{'client': 'Unknown', 'percentage': 1.0}], 'networkPenetration': 1.353048146865258e-06, 'relayerPercentages': [], 'nodeOperatorCount': 1, 'operatorTags': [{'name': 'operator', 'path': None, 'idType': None}], 'displayName': 'Audit.one', 'aprPercentage': 2.85}], 'next': None}} + utilities.print_data("fetch", response) + return response + else: + url = "https://api.rated.network/v0/eth/operators?window=7d&idType=nodeOperator&size=100" + payload = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': utilities.rated_token + } + response = utilities.fetch_json(url, "GET", payload, headers) + return response + +def process_rated_operator_node_data(raw_data): + # # example rated raw data: + # raw_data = { 'attempts': 1, 'data': { 'data': [ + # { + # 'aprPercentage': 4.93, + # 'avgCorrectness': 0.9949509406954944, + # 'avgInclusionDelay': 1.017948702586212, + # 'avgUptime': 0.9998280828900458, + # 'avgValidatorEffectiveness': 97.76418156450255, + # 'clientPercentages': [ { 'client': 'Lighthouse', + # 'percentage': 0.333947045414694}, + # { 'client': 'Nimbus', + # 'percentage': 0.0005260389268805891}, + # { 'client': 'Prysm', + # 'percentage': 0.6386989303875154}, + # { 'client': 'Teku', + # 'percentage': 0.026827985270910047}], + # 'displayName': 'Kiln', + # 'id': 'Kiln', + # 'idType': 'nodeOperator', + # 'networkPenetration': 0.032216076376861795, + # 'nodeOperatorCount': 4, + # 'operatorTags': [ { 'idType': None, + # 'name': 'selfReport', + # 'path': None}, + # { 'idType': None, + # 'name': 'operator', + # 'path': None}], + # 'relayerPercentages': [ { 'percentage': 0.09717868338557993, + # 'relayer': 'blocknative'}, + # { 'percentage': 0.012763098969995522, + # 'relayer': 'edennetwork'}, + # { 'percentage': 0.0006717420510523958, + # 'relayer': 'no_mev_boost'}, + # { 'percentage': 0.025526197939991044, + # 'relayer': 'aestus'}, + # { 'percentage': 0.17129422301836095, + # 'relayer': 'ultra_sound_money'}, + # { 'percentage': 0.24406627854903717, + # 'relayer': 'bloxroute_maxprofit'}, + # { 'percentage': 0.001567398119122257, + # 'relayer': 'manifold'}, + # { 'percentage': 0.23510971786833856, + # 'relayer': 'bloxroute_regulated'}, + # { 'percentage': 0.1334527541424093, + # 'relayer': 'agnostic'}, + # { 'percentage': 0.07836990595611286, + # 'relayer': 'flashbots'}], + # 'timeWindow': '7d', + # 'validatorCount': 23810 + # }]}} + return + + +def get_edi_marketshare_data(): + if utilities.use_test_data: + # response split into multiple lines so it can be collapsed + response = {'status': 200, 'attempts': 1, 'data': [ + {'name': 'Attestant', 'website': 'https://www.attestant.io/', 'sourceLink': 'https://www.attestant.io/posts/helping-client-diversity/', 'twitter': 'https://twitter.com/attestantio', 'reliable': False, 'clients': [{'name': 'NoGeth', 'tooltip': 'Geth is not used for attestations (no supermajority client risk). See the link for details.', 'percent': 100}], 'ratedId': 'Attestant', 'type': 'operator'}, {'name': 'Ethpool', 'website': 'https://ethpool.org/', 'source': 'Support (Discord)', 'twitter': 'https://twitter.com/ethpool_staking', 'reliable': True, 'clients': [{'name': 'Nethermind', 'percent': 100}], 'ratedId': None, 'type': 'pool'},{'name': 'StakeWise', 'website': 'https://stakewise.io/', 'source': 'Support (Discord), approximate numbers', 'twitter': 'https://twitter.com/stakewise_io', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 42.12}, {'name': 'Nethermind', 'percent': 28.94}, {'name': 'Besu', 'percent': 28.94}], 'ratedId': 'StakeWise', 'type': 'pool'}, {'name': 'Rocket Pool', 'website': 'https://rocketpool.net/', 'source': 'Discord (Tool developed by 0xinvis.eth)', 'twitter': 'https://twitter.com/Rocket_Pool', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 46.56}, {'name': 'Nethermind', 'percent': 19.3}, {'name': 'Besu', 'percent': 8.12}, {'name': 'Unknown', 'percent': 26.02}], 'ratedId': 'Rocketpool', 'type': 'pool'}, {'name': 'Lido', 'website': 'https://lido.fi/ethereum', 'sourceLink': 'https://research.lido.fi/t/lido-node-operator-validator-metrics/1431', 'twitter': 'https://twitter.com/LidoFinance', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 84.5}, {'name': 'Nethermind', 'percent': 8.1}, {'name': 'Besu', 'percent': 5.0}, {'name': 'Erigon', 'percent': 2.4}], 'ratedId': 'Lido', 'type': 'pool'}, {'name': 'Staked', 'website': 'https://staking.staked.us/ethereum-staking-options', 'source': 'Support (Discord)', 'twitter': 'https://twitter.com/staked_us', 'reliable': True, 'clients': [{'name': 'Unknown', 'tooltip': 'Allocation unknown, diversified (Geth and Erigon)', 'percent': 100}], 'ratedId': 'Staked.us', 'type': 'operator'}, {'name': 'Allnodes', 'website': 'https://www.allnodes.com/eth2/staking', 'source': 'Support (Discord)', 'twitter': 'https://twitter.com/Allnodes', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 100}], 'ratedId': 'Allnodes', 'type': 'operator'}, {'name': 'Ankr', 'website': 'https://www.ankr.com/staking-crypto/', 'source': 'Data pending', 'twitter': 'https://twitter.com/ankr', 'reliable': False, 'clients': [{'name': 'Geth', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Ankr', 'type': 'pool'}, {'name': 'Blox Staking', 'website': 'https://www.bloxstaking.com/', 'source': 'Support (Discord)', 'twitter': 'https://twitter.com/ssv_network', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 100}], 'ratedId': 'Bloxstaking', 'type': 'pool'}, {'name': 'Binance', 'website': 'https://www.binance.com/en/eth2', 'source': 'Data pending', 'twitter': 'https://twitter.com/binance', 'reliable': False, 'clients': [{'name': 'Geth', 'website': '', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Binance', 'type': 'pool'}, {'name': 'Bitcoin Suisse', 'website': 'https://www.bitcoinsuisse.com/staking/ethereum-2', 'source': 'Data pending', 'twitter': 'https://twitter.com/bitcoinsuisseag', 'reliable': False, 'clients': [{'name': 'Geth', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Bitcoin Suisse', 'type': 'pool'}, {'name': 'Bitfinex', 'website': 'https://staking.bitfinex.com/', 'source': 'Support ("Unfortunately, we are unable to provide you with the specific information you are requesting.")', 'twitter': 'https://twitter.com/bitfinex', 'reliable': False, 'clients': [{'name': 'Geth', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Bitfinex', 'type': 'pool'}, {'name': 'Coinbase', 'website': 'https://www.coinbase.com/earn/staking/ethereum', 'source': 'Data pending', 'twitter': 'https://twitter.com/coinbase', 'reliable': False, 'clients': [{'name': 'Geth', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Coinbase', 'type': 'pool'}, {'name': 'Kraken', 'website': 'https://www.kraken.com/features/staking-coins', 'source': 'Data pending', 'twitter': 'https://twitter.com/krakenfx', 'reliable': False, 'clients': [{'name': 'Geth', 'tooltip': 'Maybe 100% Geth', 'percent': 100}], 'ratedId': 'Kraken', 'type': 'pool'}, {'name': 'P2P.org', 'website': 'https://p2p.org/networks/ethereum', 'source': 'Support (Chat)', 'twitter': 'https://twitter.com/p2pvalidator', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 100}], 'ratedId': 'P2P.ORG - P2P Validator', 'type': 'operator'}, {'name': 'stakefish', 'website': 'https://stake.fish/networks/ethereum', 'source': 'Support (Telegram)', 'twitter': 'https://twitter.com/stakefish', 'reliable': True, 'clients': [{'name': 'Geth', 'percent': 100}], 'ratedId': 'Stakefish', 'type': 'pool'}]} + utilities.print_data("fetch", response) + return response + else: + url = "https://raw.githubusercontent.com/one-three-three-seven/execution-diversity/main/services.json" + response = utilities.fetch_json(url) + return response + +def process_edi_marketshare_data(raw_data, pool_validator_counts, total_validator_count): + client_counts = { + "geth": 0, + "erigon": 0, + "nethermind": 0, + "besu": 0, + "reth": 0, + "unknown": 0, + "other": 0 + } + nogeth_clients = ["erigon", "nethermind", "besu"] + validators_represented = 0 + validators_percentage = 0 + threshold_percentage = 0.5 # represented as a percent, not a decimal + sample_size = 0 + cleaned_data = [] + added_data = [] + removed_data = [] + reformatted_data = [] + calculated_data = [] + marketshare_data = [] + extra_data = {} + final_data = {} + + # cleaned data + cleaned_data = copy.deepcopy(raw_data["data"]) + for item in cleaned_data: + # delete irrelevent keys + delete_keys = ["source", "sourceLink", "twitter", "website"] + for key in delete_keys: + if key in item: + del item[key] + for client in item["clients"]: + # make client names lowercase + client["name"] = client["name"].lower().strip() + if "tooltip" in client: + del client["tooltip"] + if "website" in client: + del client["website"] + # turn client percentages into a decimal + client["percent"] = client["percent"] / 100 + # utilities.pprint(["cleaned_data", cleaned_data]) + + # add data + added_data = copy.deepcopy(cleaned_data) + for item in added_data: + if item["ratedId"] in pool_validator_counts: + validator_count = pool_validator_counts[item["ratedId"]] + item["validator_count"] = validator_count + # utilities.pprint(["added_data", added_data]) + + # remove data with incomplete data + for item in added_data: + if item["ratedId"] != None and item["ratedId"] in pool_validator_counts: + removed_data.append(item) + # utilities.pprint(["removed_data", removed_data]) + + # calculate the marketshare for each client + calculated_data = copy.deepcopy(removed_data) + for item in calculated_data: + for client in item["clients"]: + validator_split = item["validator_count"] * client["percent"] + # split the number of nogeth validators among nogeth_clients + if client["name"] == "nogeth": + for nogeth_client in nogeth_clients: + client_counts["nogeth_client"] += validator_split + # tally the number of each explicit client + else: + if client["name"] in client_counts: + client_counts[client["name"]] += validator_split + # if the client isn't in the clients list, add it to "other" + else: + client_counts["other"] += validator_split + validators_represented += item["validator_count"] + # utilities.pprint(["calculated_data", calculated_data]) + # utilities.pprint(["validators_represented", validators_represented]) + + # reformat data into a list of dicts + for key, value in client_counts.items(): + percentage = value / validators_represented + marketshare_data.append({"name": key, "value": percentage, "accuracy": "no data"}) + # utilities.pprint(["marketshare_data", marketshare_data]) + + # sort the list by marketshare descending + sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) + # utilities.pprint(["sorted_data", sorted_data]) + + # supplemental data + extra_data["data_source"] = "edi" + extra_data["has_majority"] = False + extra_data["has_supermajority"] = False + extra_data["danger_client"] = "" + if sorted_data[0]["value"] >= .50: + extra_data["has_majority"] = True + extra_data["danger_client"] = sorted_data[0]["name"] + if sorted_data[0]["value"] >= .66: + extra_data["has_supermajority"] = True + extra_data["top_client"] = sorted_data[0]["name"] + extra_data["validators_represented"] = validators_represented + extra_data["validators_total"] = total_validator_count + validators_percentage = validators_represented / total_validator_count + extra_data["validators_percentage"] = validators_percentage + # utilities.pprint(["extra_data", extra_data]) + + # create final data dict + final_data["distribution"] = sorted_data + final_data["other"] = extra_data + utilities.print_data("processed", final_data, "final_data_edi") + + return final_data + + +def edi_marketshare(): + raw_overview_data = get_rated_overview_data() + utilities.save_to_file("../_data/raw/rated_overview_raw.json", raw_overview_data) + total_validator_count = process_rated_overview_data(raw_overview_data) + + + raw_pool_data = get_rated_operator_pool_data() + utilities.save_to_file("../_data/raw/rated_operator_pool_raw.json", raw_pool_data) + pool_validator_counts = process_rated_operator_pool_data(raw_pool_data) + + + # raw_node_data = get_rated_operator_node_data() + # utilities.save_to_file("../_data/raw/rated_operator_node_raw.json", raw_node_data) + # processed_node_data = process_rated_operator_node_data(raw_node_data) + + + raw_marketshare_data = get_edi_marketshare_data() + utilities.save_to_file("../_data/raw/edi_raw.json", raw_marketshare_data) + processed_marketshare_data = process_edi_marketshare_data(raw_marketshare_data, pool_validator_counts, total_validator_count) + + utilities.save_to_file("../_data/edi.json", processed_marketshare_data) + + +######################################## + + +def get_rated_marketshare_data(): + if utilities.use_test_data: + # response split into multiple lines so it can be collapsed + response = { + 'status': 200, 'attempts': 1, 'data': [ + {'timeWindow': 'all', 'validatorCount': 732484, 'validatorCountDiff': 0, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 0, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': 0.0, 'consensusLayerRewardsPercentage': 70.89001418712094, 'priorityFeesPercentage': 22.444404751981917, 'baselineMevPercentage': 6.665581060897138, 'avgValidatorEffectiveness': 96.34926615541492, 'avgInclusionDelay': 1.02674142057442, 'avgUptime': 99.64490933047226, 'sumMissedSlots': 72749, 'missedSlotsPercentage': 1.022675595110489, 'avgConsensusAprPercentage': 3.5752906143040857, 'avgExecutionAprPercentage': 1.4681427314232345, 'medianConsensusAprPercentage': 3.466286508072917, 'medianExecutionAprPercentage': 0.4706290926215278, 'consensusRewardsRatio': 0.7089001418712094, 'executionRewardsRatio': 0.2910998581287906, 'avgNetworkAprPercentage': 5.04343334572732, 'medianNetworkAprPercentage': 3.936915600694445, 'avgConsensusAprGwei': 1144092997, 'avgExecutionAprGwei': 469805674, 'medianConsensusAprGwei': 1109211683, 'medianExecutionAprGwei': 150601310, 'avgNetworkAprGwei': 1613898671, 'medianNetworkAprGwei': 1259812992, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '7d', 'validatorCount': 732484, 'validatorCountDiff': 15285, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 489120000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': -1584026.892314911, 'consensusLayerRewardsPercentage': 74.17879638365959, 'priorityFeesPercentage': 18.16255691339244, 'baselineMevPercentage': 7.658646702947978, 'avgValidatorEffectiveness': 96.96456915550357, 'avgInclusionDelay': 1.0233240107783756, 'avgUptime': 99.5917567250837, 'sumMissedSlots': 356, 'missedSlotsPercentage': 0.7063492063492063, 'avgConsensusAprPercentage': 3.3712964163931844, 'avgExecutionAprPercentage': 1.1735284941601334, 'medianConsensusAprPercentage': 2.8652880479910716, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.7417879638365958, 'executionRewardsRatio': 0.25821203616340416, 'avgNetworkAprPercentage': 4.544824910553318, 'medianNetworkAprPercentage': 2.8652880479910716, 'avgConsensusAprGwei': 1078814853, 'avgExecutionAprGwei': 375529118, 'medianConsensusAprGwei': 916892175, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1454343971, 'medianNetworkAprGwei': 916892175, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '30d', 'validatorCount': 732484, 'validatorCountDiff': 60820, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 1946240000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': -2782230.2838668823, 'consensusLayerRewardsPercentage': 70.19038984546984, 'priorityFeesPercentage': 20.892577953835676, 'baselineMevPercentage': 8.917032200694484, 'avgValidatorEffectiveness': 97.08324599003156, 'avgInclusionDelay': 1.0218653815464485, 'avgUptime': 99.60600150408419, 'sumMissedSlots': 1589, 'missedSlotsPercentage': 0.7356481481481482, 'avgConsensusAprPercentage': 3.4431767042864805, 'avgExecutionAprPercentage': 1.462304960464106, 'medianConsensusAprPercentage': 2.9132289458333336, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.7019038984546984, 'executionRewardsRatio': 0.29809610154530164, 'avgNetworkAprPercentage': 4.905481664750587, 'medianNetworkAprPercentage': 2.9132289458333336, 'avgConsensusAprGwei': 1101816545, 'avgExecutionAprGwei': 467937587, 'medianConsensusAprGwei': 932233263, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1569754133, 'medianNetworkAprGwei': 932233263, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}, {'timeWindow': '1d', 'validatorCount': 732484, 'validatorCountDiff': 2183, 'medianValidatorAgeDays': 311, 'activeStake': 23439488000000000, 'activeStakeDiff': 69856000000000, 'avgValidatorBalance': 32139253647.151455, 'avgValidatorBalanceDiff': 220670.82093429565, 'consensusLayerRewardsPercentage': 68.76553541277683, 'priorityFeesPercentage': 22.120531489097694, 'baselineMevPercentage': 9.113933098125479, 'avgValidatorEffectiveness': 97.30381474144272, 'avgInclusionDelay': 1.0206948586335172, 'avgUptime': 99.64239298136275, 'sumMissedSlots': 51, 'missedSlotsPercentage': 0.7083333333333333, 'avgConsensusAprPercentage': 3.3542611110708775, 'avgExecutionAprPercentage': 1.5235619014838189, 'medianConsensusAprPercentage': 2.8587050937500003, 'medianExecutionAprPercentage': 0.0, 'consensusRewardsRatio': 0.6876553541277683, 'executionRewardsRatio': 0.3123446458722317, 'avgNetworkAprPercentage': 4.877823012554696, 'medianNetworkAprPercentage': 2.8587050937500003, 'avgConsensusAprGwei': 1073363556, 'avgExecutionAprGwei': 487539808, 'medianConsensusAprGwei': 914785630, 'medianExecutionAprGwei': 0, 'avgNetworkAprGwei': 1560903364, 'medianNetworkAprGwei': 914785630, 'giniCoefficient': 0.9593938319220118, 'clientPercentages': [{'client': 'Teku', 'percentage': 0.18995044012847778}, {'client': 'Lighthouse', 'percentage': 0.3653671935935509}, {'client': 'Nimbus', 'percentage': 0.03222118333276813}, {'client': 'Prysm', 'percentage': 0.401267821933271}, {'client': 'Lodestar', 'percentage': 0.011193361011932157}], 'clientValidatorEffectiveness': [{'client': 'Lighthouse', 'avgValidatorEffectiveness': 97.25144}, {'client': 'Lodestar', 'avgValidatorEffectiveness': 97.52908}, {'client': 'Nimbus', 'avgValidatorEffectiveness': 96.08916}, {'client': 'Prysm', 'avgValidatorEffectiveness': 97.11941}, {'client': 'Teku', 'avgValidatorEffectiveness': 97.38093}], 'latestEpoch': 222326, 'activationQueueMinutes': 37802.66667, 'activatingValidators': 63443, 'activatingStake': 2030176000000000, 'exitQueueMinutes': 32.0, 'withdrawalQueueMinutes': 1644.8, 'withdrawalProcessingQueueMinutes': 10400.825, 'fullyWithdrawingValidators': 585, 'partiallyWithdrawingValidators': 699897, 'totalWithdrawingValidators': 700482, 'fullyWithdrawingBalance': 24895807435429, 'partiallyWithdrawingBalance': 6499611370618, 'totalWithdrawingBalance': 31395418806047, 'exitingValidators': 2, 'exitingStake': 64000000000}]} + utilities.print_data("fetch", response) + return response + else: + url = "https://api.rated.network/v0/eth/network/overview" + payload = {} + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': utilities.rated_token + } + response = utilities.fetch_json(url, "GET", payload, headers) + return response + +def process_rated_marketshare_data(raw_data): + # example rated raw data: + # raw_data = {'attempts': 1,'data': [ + # {'clientPercentages': [ + # {'client': 'Teku', + # 'percentage': 0.18995044012847778}, + # {'client': 'Lighthouse', + # 'percentage': 0.3653671935935509}, + # {'client': 'Nimbus', + # 'percentage': 0.03222118333276813}, + # {'client': 'Prysm', + # 'percentage': 0.401267821933271}, + # {'client': 'Lodestar', + # 'percentage': 0.011193361011932157}], + + main_clients = ["geth", "erigon", "nethermind", "besu", "reth"] + threshold_percentage = 0.5 # represented as a percent, not a decimal + sample_size = 0 + reformatted_data = [] + filtered_data = [{"name": "other", "value": 0}] + marketshare_data = [] + extra_data = {} + final_data = {} + + # reformat data into a list of dicts + for item in raw_data["data"][0]["clientPercentages"]: + reformatted_data.append({"name": item["client"].lower(), "value": item["percentage"]}) + # utilities.pprint(["reformatted_data", reformatted_data]) + + # filter out items either under the threshold and not in the main_clients list + for item in reformatted_data: + if item["name"] in main_clients: + filtered_data.append({"name": item["name"], "value": item["value"]}) + elif (item["value"] * 100) >= threshold_percentage: + filtered_data.append({"name": item["name"], "value": item["value"]}) + else: + filtered_data[0]["value"] += item["value"] + # utilities.pprint(["filtered_data", filtered_data]) + + # calculate the marketshare for each client + for item in filtered_data: + marketshare = item["value"] + marketshare_data.append({"name": item["name"], "value": marketshare, "accuracy": "no data"}) + # utilities.pprint(["marketshare_data", marketshare_data]) + + # sort the list by marketshare descending + sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) + # utilities.pprint(["sorted_data", sorted_data]) + + # supplemental data + extra_data["data_source"] = "rated" + extra_data["has_majority"] = False + extra_data["has_supermajority"] = False + extra_data["danger_client"] = "" + if sorted_data[0]["value"] >= .50: + extra_data["has_majority"] = True + extra_data["danger_client"] = sorted_data[0]["name"] + if sorted_data[0]["value"] >= .66: + extra_data["has_supermajority"] = True + extra_data["top_client"] = sorted_data[0]["name"] + # utilities.pprint(["extra_data", extra_data]) + + # create final data dict + final_data["distribution"] = sorted_data + final_data["other"] = extra_data + utilities.print_data("processed", final_data, "final_data_rated") + + return final_data + +def rated_marketshare(): + raw_data = get_rated_marketshare_data() + utilities.save_to_file("../_data/raw/rated_raw.json", raw_data) + processed_data = process_rated_marketshare_data(raw_data) + utilities.save_to_file("../_data/rated.json", processed_data) + + +######################################## + + +def get_blockprint_marketshare_data(): + if utilities.use_test_data: + response = {'status': 200, 'attempts': 1, 'data': {'Uncertain': 0, 'Grandine': 0, 'Lighthouse': 33411, 'Lodestar': 1145, 'Nimbus': 4862, 'Other': 0, 'Prysm': 45450, 'Teku': 15458}} + utilities.print_data("fetch", response) + return response + else: + initial_timestamp = 1606824023 # seconds + initial_epoch = 0 + delta_timestamp = current_time - initial_timestamp # seconds + current_epoch = math.floor(delta_timestamp / 384) + + # the Blockprint API caches results so fetching data based on an "epoch day" so + # everyone that loads the page on an "epoch day" will use the cached results and + # their backend doesn't get overloaded + # Michael Sproul recommends using a 2-week period + end_epoch = math.floor(current_epoch / 225) * 225 + start_epoch = end_epoch - 3150 + url = f"https://api.blockprint.sigp.io/blocks_per_client/{start_epoch}/{end_epoch}" + response = utilities.fetch_json(url) + return response + +def get_blockprint_accuracy_data(): + # accuracy of fingerprinting for each client + if utilities.use_test_data: + response = {'status': 200, 'attempts': 1, 'data': {'clients': {'Lighthouse': {'true_positives': 3644, 'true_negatives': 6921, 'false_positives': 377, 'false_negatives': 37, 'false_negatives_detail': {'Nimbus': 20, 'Prysm': 17}}, 'Nimbus': {'true_positives': 0, 'true_negatives': 10822, 'false_positives': 157, 'false_negatives': 0, 'false_negatives_detail': {}}, 'Prysm': {'true_positives': 5147, 'true_negatives': 5387, 'false_positives': 36, 'false_negatives': 409, 'false_negatives_detail': {'Lighthouse': 376, 'Nimbus': 30, 'Teku': 3}}, 'Teku': {'true_positives': 1615, 'true_negatives': 9234, 'false_positives': 3, 'false_negatives': 127, 'false_negatives_detail': {'Lighthouse': 1, 'Nimbus': 107, 'Prysm': 19}}}, 'nodes': [{'name': 'prysm-subscribe-all', 'label': 'Prysm', 'true_positives': 3648, 'false_negatives': {'Lighthouse': 3, 'Nimbus': 28, 'Teku': 2}, 'latest_slot': 7102453}, {'name': 'prysm-subscribe-none', 'label': 'Prysm', 'true_positives': 1499, 'false_negatives': {'Lighthouse': 373, 'Nimbus': 2, 'Teku': 1}, 'latest_slot': 7102453}, {'name': 'teku-subscribe-all', 'label': 'Teku', 'true_positives': 1615, 'false_negatives': {'Lighthouse': 1, 'Nimbus': 107, 'Prysm': 19}, 'latest_slot': 7102453}, {'name': 'lighthouse-subscribe-none', 'label': 'Lighthouse', 'true_positives': 3644, 'false_negatives': {'Nimbus': 20, 'Prysm': 17}, 'latest_slot': 7102453}]}} + utilities.print_data("fetch", response) + return response + else: + url = "https://api.blockprint.sigp.io/confusion" + response = utilities.fetch_json(url) + return response + + +def process_blockprint_accuracy_data(raw_data): + # example blockprint raw data: + # raw_data = {'status': 200, 'attempts': 1, 'data': { + # 'clients': { + # 'Lighthouse': { + # 'true_positives': 3644, 'true_negatives': 6921, 'false_positives': 377, + # 'false_negatives': 37, 'false_negatives_detail': {'Nimbus': 20, 'Prysm': 17}}, + # 'Nimbus': { + # 'true_positives': 0, 'true_negatives': 10822, 'false_positives': 157, + # 'false_negatives': 0, 'false_negatives_detail': {}}, + # 'Prysm': { + # 'true_positives': 5147, 'true_negatives': 5387, 'false_positives': 36, + # 'false_negatives': 409, 'false_negatives_detail': {'Lighthouse': 376, 'Nimbus': 30, 'Teku': 3}}, + # 'Teku': { + # 'true_positives': 1615, 'true_negatives': 9234, 'false_positives': 3, + # 'false_negatives': 127, 'false_negatives_detail': {'Lighthouse': 1, 'Nimbus': 107, 'Prysm': 19}} + # }, + # 'nodes': [ + # {'name': 'prysm-subscribe-all', 'label': 'Prysm', 'true_positives': 3648, + # 'false_negatives': {'Lighthouse': 3, 'Nimbus': 28, 'Teku': 2}, 'latest_slot': 7102453}, + # {'name': 'prysm-subscribe-none', 'label': 'Prysm', 'true_positives': 1499, + # 'false_negatives': {'Lighthouse': 373, 'Nimbus': 2, 'Teku': 1}, 'latest_slot': 7102453}, + # {'name': 'teku-subscribe-all', 'label': 'Teku', 'true_positives': 1615, + # 'false_negatives': {'Lighthouse': 1, 'Nimbus': 107, 'Prysm': 19}, 'latest_slot': 7102453}, + # {'name': 'lighthouse-subscribe-none', 'label': 'Lighthouse', 'true_positives': 3644, + # 'false_negatives': {'Nimbus': 20, 'Prysm': 17}, 'latest_slot': 7102453} + # ]}} + + # calculate the accuracy for each client + accuracy_data = [] + for key, value in raw_data["data"]["clients"].items(): + if (value["true_positives"] == 0 and value["false_negatives"] == 0): + accuracy = "no data"; + elif (value["true_positives"] == 0 and value["false_negatives"] != 0): + accuracy = "0"; + else: + accuracy = round(value["true_positives"] / (value["true_positives"] + value["false_negatives"]), 6) + accuracy_data.append({"name": key.lower(), "value": accuracy}) + utilities.print_data("processed", accuracy_data, "blockprint_accuracy_data") + return accuracy_data + +def process_blockprint_marketshare_data(raw_marketshare_data, processed_accuracy_data): + # example blockprint raw data: + # raw_marketshare_data = {'status': 200, 'attempts': 1, 'data': { + # 'Uncertain': 0, + # 'Grandine': 0, + # 'Lighthouse': 33411, + # 'Lodestar': 1145, + # 'Nimbus': 4862, + # 'Other': 0, + # 'Prysm': 45450, + # 'Teku': 15458 + # }} + + main_clients = ["lighthouse", "nimbus", "teku", "prysm", "lodestar", "erigon", "grandine"] + threshold_percentage = 0.5 # represented as a percent, not a decimal + sample_size = 0 + reformatted_data = [] + filtered_data = [{"name": "other", "value": 0}] + marketshare_data = [] + extra_data = {} + final_data = {} + + # reformat data into a list of dicts + for key, value in raw_marketshare_data["data"].items(): + reformatted_data.append({"name": key.lower(), "value": value}) + sample_size += value + # utilities.pprint(["reformatted_data", reformatted_data]) + # utilities.pprint(["sample_size", sample_size]) + + # filter out items either under the threshold and not in the main_clients list + for item in reformatted_data: + if item["name"] in main_clients: + filtered_data.append({"name": item["name"], "value": item["value"]}) + elif (item["value"] / sample_size * 100) >= threshold_percentage: + filtered_data.append({"name": item["name"], "value": item["value"]}) + else: + filtered_data[0]["value"] += item["value"] + # utilities.pprint(["filtered_data", filtered_data]) + + # calculate the marketshare for each client + for item in filtered_data: + marketshare = item["value"] / sample_size + accuracy = "no data" + for x in processed_accuracy_data: + if x["name"] == item["name"]: + accuracy = x["value"] + marketshare_data.append({"name": item["name"], "value": marketshare, "accuracy": accuracy}) + # utilities.pprint(["marketshare_data", marketshare_data]) + + # sort the list by marketshare descending + sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) + # utilities.pprint(["sorted_data", sorted_data]) + + # supplemental data + extra_data["data_source"] = "blockprint" + extra_data["has_majority"] = False + extra_data["has_supermajority"] = False + extra_data["danger_client"] = "" + if sorted_data[0]["value"] >= .50: + extra_data["has_majority"] = True + extra_data["danger_client"] = sorted_data[0]["name"] + if sorted_data[0]["value"] >= .66: + extra_data["has_supermajority"] = True + extra_data["top_client"] = sorted_data[0]["name"] + # utilities.pprint(["extra_data", extra_data]) + + # create final data dict + final_data["distribution"] = sorted_data + # final_data["accuracy"] = processed_accuracy_data + final_data["other"] = extra_data + utilities.print_data("processed", final_data, "final_data_blockprint") + + return final_data + + +def blockprint_marketshare(): + raw_marketshare_data = get_blockprint_marketshare_data() + utilities.save_to_file("../_data/raw/blockprint_raw.json", raw_marketshare_data) + raw_accuracy_data = get_blockprint_accuracy_data() + utilities.save_to_file("../_data/raw/blockprint_accuracy_raw.json", raw_accuracy_data) + processed_accuracy_data = process_blockprint_accuracy_data(raw_accuracy_data) + processed_marketshare_data = process_blockprint_marketshare_data(raw_marketshare_data, processed_accuracy_data) + utilities.save_to_file("../_data/blockprint.json", processed_marketshare_data) + + +######################################## + + +def get_ethernodes_marketshare_data(): + if utilities.use_test_data: + response = {'status': 200, 'attempts': 1, 'data': [{'client': 'geth', 'value': 2508}, {'client': 'nethermind', 'value': 1199}, {'client': 'erigon', 'value': 699}, {'client': 'besu', 'value': 596}, {'client': 'reth', 'value': 9}, {'client': 'teth', 'value': 3}]} + utilities.print_data("fetch", response) + return response + else: + url = "https://ethernodes.org/api/clients" + response = utilities.fetch_json(url) + return response + +def process_ethernodes_marketshare_data(raw_data): + # example ethernodes raw data: + # raw_data = {'status': 200, 'attempts': 1, 'data': [ + # {'client': 'geth', 'value': 2508}, + # {'client': 'nethermind', 'value': 1199}, + # {'client': 'erigon', 'value': 699}, + # {'client': 'besu', 'value': 596}, + # {'client': 'reth', 'value': 9}, + # {'client': 'teth', 'value': 3} + # ]} + + main_clients = ["geth", "erigon", "nethermind", "besu", "reth"] + threshold_percentage = 0.5 # represented as a percent, not a decimal + sample_size = 0 + reformatted_data = [] + filtered_data = [{"name": "other", "value": 0}] + marketshare_data = [] + extra_data = {} + final_data = {} + + # reformat data into a list of dicts + for item in raw_data["data"]: + reformatted_data.append({"name": item["client"].lower(), "value": item["value"]}) + sample_size += item["value"] + # utilities.pprint(["reformatted_data", reformatted_data]) + # utilities.pprint(["sample_size", sample_size]) + + # filter out items either under the threshold and not in the main_clients list + for item in reformatted_data: + if item["name"] in main_clients: + filtered_data.append({"name": item["name"], "value": item["value"]}) + elif (item["value"] / sample_size * 100) >= threshold_percentage: + filtered_data.append({"name": item["name"], "value": item["value"]}) + else: + filtered_data[0]["value"] += item["value"] + # utilities.pprint(["filtered_data", filtered_data]) + + # calculate the marketshare for each client + for item in filtered_data: + marketshare = item["value"] / sample_size + marketshare_data.append({"name": item["name"], "value": marketshare, "accuracy": "no data"}) + # utilities.pprint(["marketshare_data", marketshare_data]) + + # sort the list by marketshare descending + sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) + # utilities.pprint(["sorted_data", sorted_data]) + + # supplemental data + extra_data["data_source"] = "ethernodes" + extra_data["has_majority"] = False + extra_data["has_supermajority"] = False + extra_data["danger_client"] = "" + if sorted_data[0]["value"] >= .50: + extra_data["has_majority"] = True + extra_data["danger_client"] = sorted_data[0]["name"] + if sorted_data[0]["value"] >= .66: + extra_data["has_supermajority"] = True + extra_data["top_client"] = sorted_data[0]["name"] + # utilities.pprint(["extra_data", extra_data]) + + # create final data dict + final_data["distribution"] = sorted_data + final_data["other"] = extra_data + utilities.print_data("processed", final_data, "final_data_ethernodes") + + return final_data + +def ethernodes_marketshare(): + raw_data = get_ethernodes_marketshare_data() + utilities.save_to_file("../_data/raw/ethernodes_raw.json", raw_data) + processed_data = process_ethernodes_marketshare_data(raw_data) + utilities.save_to_file("../_data/ethernodes.json", processed_data) + + +######################################## + + +# def get_migalabs_marketshare_data(): +# if utilities.use_test_data: +# response = {'status': 200, 'attempts': 1, 'data': [{"timestamp":"2023-10-03T04:37:09Z","data":[{"client_name":"lighthouse","node_count":2867},{"client_name":"prysm","node_count":2206},{"client_name":"teku","node_count":1303},{"client_name":"nimbus","node_count":836},{"client_name":"lodestar","node_count":252},{"client_name":"grandine","node_count":213},{"client_name":"unknown","node_count":28}]}]} +# utilities.print_data("fetch", response) +# return response +# else: +# url = "https://monitoreth.io/data-api/api/eth/v1/nodes/consensus/validators/client_diversity" +# payload = {} +# headers = { +# 'X-Api-Key': migalabs_token +# } +# response = utilities.fetch_json(url, "GET", payload, headers) +# return response + +# def process_migalabs_marketshare_data(raw_data): +# # example migalabs raw data: +# # raw_data = {'status': 200, 'attempts': 1, 'data': [{ +# # "timestamp": "2023-10-03T04:37:09Z", +# # "data": [ +# # { +# # "client_name": "lighthouse", +# # "node_count": 2867 +# # }, +# # { +# # "client_name": "prysm", +# # "node_count": 2206 +# # }, +# # { +# # "client_name": "teku", +# # "node_count": 1303 +# # }, +# # { +# # "client_name": "nimbus", +# # "node_count": 836 +# # }, +# # { +# # "client_name": "lodestar", +# # "node_count": 252 +# # }, +# # { +# # "client_name": "grandine", +# # "node_count": 213 +# # }, +# # { +# # "client_name": "unknown", +# # "node_count": 28 +# # } +# # ] +# # }]} + +# main_clients = ["lighthouse", "nimbus", "teku", "prysm", "lodestar", "erigon", "grandine"] +# threshold_percentage = 0.5 # represented as a percent, not a decimal +# sample_size = 0 +# reformatted_data = [] +# filtered_data = [{"name": "other", "value": 0}] +# marketshare_data = [] +# extra_data = {} +# final_data = {} + +# # reformat data into a list of dicts +# for item in raw_data["data"][0]["data"]: +# reformatted_data.append({"name": item["client_name"].lower(), "value": item["node_count"]}) +# sample_size += item["node_count"] +# # utilities.pprint(["reformatted_data", reformatted_data]) +# # utilities.pprint(["sample_size", sample_size]) + +# # filter out items either under the threshold and not in the main_clients list +# for item in reformatted_data: +# if item["name"] in main_clients: +# filtered_data.append({"name": item["name"], "value": item["value"]}) +# elif (item["value"] / sample_size * 100) >= threshold_percentage: +# filtered_data.append({"name": item["name"], "value": item["value"]}) +# else: +# filtered_data[0]["value"] += item["value"] +# # utilities.pprint(["filtered_data", filtered_data]) + +# # calculate the marketshare for each client +# for item in filtered_data: +# marketshare = item["value"] / sample_size +# marketshare_data.append({"name": item["name"], "value": marketshare, "accuracy": "no data"}) +# # utilities.pprint(["marketshare_data", marketshare_data]) + +# # sort the list by marketshare descending +# sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) +# # utilities.pprint(["sorted_data", sorted_data]) + +# # supplemental data +# extra_data["data_source"] = "migalabs" +# extra_data["has_majority"] = False +# extra_data["has_supermajority"] = False +# extra_data["danger_client"] = "" +# if sorted_data[0]["value"] >= .50: +# extra_data["has_majority"] = True +# extra_data["danger_client"] = sorted_data[0]["name"] +# if sorted_data[0]["value"] >= .66: +# extra_data["has_supermajority"] = True +# extra_data["top_client"] = sorted_data[0]["name"] +# # utilities.pprint(["extra_data", extra_data]) + +# # create final data dict +# final_data["distribution"] = sorted_data +# final_data["other"] = extra_data +# utilities.print_data("processed", final_data, "final_data_migalabs") + +# return final_data + +# def migalabs_marketshare(): +# raw_data = get_migalabs_marketshare_data() +# utilities.save_to_file("../_data/raw/migalabs_raw.json", raw_data["data"]) +# processed_data = process_migalabs_marketshare_data(raw_data) +# utilities.save_to_file("../_data/migalabs.json", processed_data) + + +######################################## + + +def get_data(): + # edi_marketshare() + # rated_marketshare() + # blockprint_marketshare() + # ethernodes_marketshare() + migalabs_marketshare() + + +get_data() + + diff --git a/_scripts/consensus_client_diversity_validators/__init__.py b/_scripts/consensus_client_diversity_validators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/_scripts/consensus_client_diversity_validators/migalabs.py b/_scripts/consensus_client_diversity_validators/migalabs.py new file mode 100644 index 0000000..4d679cc --- /dev/null +++ b/_scripts/consensus_client_diversity_validators/migalabs.py @@ -0,0 +1,116 @@ +import sys +sys.path.append("..") +import utilities + +def get_migalabs_marketshare_data(): + if utilities.use_test_data: + response = {'status': 200, 'attempts': 1, 'data': [{"timestamp":"2023-10-03T04:37:09Z","data":[{"client_name":"lighthouse","node_count":2867},{"client_name":"prysm","node_count":2206},{"client_name":"teku","node_count":1303},{"client_name":"nimbus","node_count":836},{"client_name":"lodestar","node_count":252},{"client_name":"grandine","node_count":213},{"client_name":"unknown","node_count":28}]}]} + # utilities.print_data("fetch", response) + return response + else: + print("AAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH") + # url = "https://monitoreth.io/data-api/api/eth/v1/nodes/consensus/validators/client_diversity" + # payload = {} + # headers = { + # 'X-Api-Key': utilities.migalabs_token + # } + # response = fetch_json(url, "GET", payload, headers) + # return response + +def process_migalabs_marketshare_data(raw_data): + # example migalabs raw data: + # raw_data = {'status': 200, 'attempts': 1, 'data': [{ + # "timestamp": "2023-10-03T04:37:09Z", + # "data": [ + # { + # "client_name": "lighthouse", + # "node_count": 2867 + # }, + # { + # "client_name": "prysm", + # "node_count": 2206 + # }, + # { + # "client_name": "teku", + # "node_count": 1303 + # }, + # { + # "client_name": "nimbus", + # "node_count": 836 + # }, + # { + # "client_name": "lodestar", + # "node_count": 252 + # }, + # { + # "client_name": "grandine", + # "node_count": 213 + # }, + # { + # "client_name": "unknown", + # "node_count": 28 + # } + # ] + # }]} + + main_clients = ["lighthouse", "nimbus", "teku", "prysm", "lodestar", "erigon", "grandine"] + threshold_percentage = 0.5 # represented as a percent, not a decimal + sample_size = 0 + reformatted_data = [] + filtered_data = [{"name": "other", "value": 0}] + marketshare_data = [] + extra_data = {} + final_data = {} + + # reformat data into a list of dicts + for item in raw_data["data"][0]["data"]: + reformatted_data.append({"name": item["client_name"].lower(), "value": item["node_count"]}) + sample_size += item["node_count"] + # utilities.pprint(["reformatted_data", reformatted_data]) + # utilities.pprint(["sample_size", sample_size]) + + # filter out items either under the threshold and not in the main_clients list + for item in reformatted_data: + if item["name"] in main_clients: + filtered_data.append({"name": item["name"], "value": item["value"]}) + elif (item["value"] / sample_size * 100) >= threshold_percentage: + filtered_data.append({"name": item["name"], "value": item["value"]}) + else: + filtered_data[0]["value"] += item["value"] + # utilities.pprint(["filtered_data", filtered_data]) + + # calculate the marketshare for each client + for item in filtered_data: + marketshare = item["value"] / sample_size + marketshare_data.append({"name": item["name"], "value": marketshare, "accuracy": "no data"}) + # utilities.pprint(["marketshare_data", marketshare_data]) + + # sort the list by marketshare descending + sorted_data = sorted(marketshare_data, key=lambda k : k['value'], reverse=True) + # utilities.pprint(["sorted_data", sorted_data]) + + # supplemental data + extra_data["data_source"] = "migalabs" + extra_data["has_majority"] = False + extra_data["has_supermajority"] = False + extra_data["danger_client"] = "" + if sorted_data[0]["value"] >= .50: + extra_data["has_majority"] = True + extra_data["danger_client"] = sorted_data[0]["name"] + if sorted_data[0]["value"] >= .66: + extra_data["has_supermajority"] = True + extra_data["top_client"] = sorted_data[0]["name"] + # pprint(["extra_data", extra_data]) + + # create final data dict + final_data["distribution"] = sorted_data + final_data["other"] = extra_data + utilities.print_data("processed", final_data, "final_data_migalabs") + + return final_data + +def migalabs_marketshare(): + raw_data = get_migalabs_marketshare_data() + utilities.save_to_file("../_data/raw/migalabs_raw.json", raw_data["data"]) + processed_data = process_migalabs_marketshare_data(raw_data) + utilities.save_to_file("../_data/migalabs.json", processed_data) diff --git a/_scripts/utilities.py b/_scripts/utilities.py new file mode 100644 index 0000000..bf787ed --- /dev/null +++ b/_scripts/utilities.py @@ -0,0 +1,157 @@ +import requests +import os +import time +import json +import copy +import pprint +from datetime import datetime, timezone + + +current_time = round(time.time()) # seconds +date = datetime.now(timezone.utc).strftime('%Y-%m-%d') # yyyy-mm-dd +print(f"Epoch: {current_time}") +print(f"Date: {date}") + +pp = pprint.PrettyPrinter(indent=4) +use_test_data = True +print_logs = True +pretty_print = True +exit_on_fetch_error = True +exit_on_save_error = True +exit_on_report_error = False + +rated_token = os.environ.get("RATED_API_KEY") +migalabs_token = os.environ.get("MIGALABS_API_KEY") +google_form_error_report_url = os.environ.get("ERROR_REPORT_ENDPOINT") + +# enter values for local testing +# rated_token = "" +# google_form_error_report_url = "" + + +def fetch_json(url, method="GET", payload={}, headers={}, retries=2): + print(f"Fetch: {url}") + response = {"status": 0, "attempts": 0, "data": None} + try: + while response["attempts"] <= retries and (response["status"] != 200 or response["data"] == None): + rate_limited_domains = ["rated.network"] + rate_limited = any(domain in url for domain in rate_limited_domains) + if (rate_limited or response["attempts"] > 0): + time.sleep(1.05) + response["attempts"] = response["attempts"] + 1 + r = requests.request(method, url, headers=headers, data=payload) + response = {"status": r.status_code, "attempts": response["attempts"], "data": r.json()} + except: + error = f"Fetch failed: {url}" + report_error(error) + if exit_on_fetch_error: + raise SystemExit(error) + else: + print(error) + finally: + context = f"Fetch {url}" + print_data(data=response, context=context) + return response + +def save_to_file(rel_path, data): + if not rel_path.startswith("/"): + rel_path = "/" + rel_path + abs_path = os.path.dirname(__file__) + rel_path + # skip file save if using test data + if use_test_data: + return + else: + todays_data = { + "date":date, + "timestamp":current_time, + "data":data + } + # check if file exists yet + if os.path.isfile(abs_path): + try: + with open(abs_path, 'r') as f: + all_data = json.load(f) + # check if there's already data for today + if date != all_data[-1]['date'] and all_data[-1]['data'] != None: + # append todays data to historical data and write to file + all_data.append(todays_data) + with open(abs_path, 'w') as f: + json.dump(all_data, f, indent=None, separators=(',', ':')) + f.close() + print(f"{rel_path} data has been updated") + # if the data was null then overwrite it + elif date == all_data[-1]['date'] and all_data[-1]['data'] == None: + del all_data[-1] + # append todays data to historical data and write to file + all_data.append(todays_data) + with open(abs_path, 'w') as f: + json.dump(all_data, f, indent=None, separators=(',', ':')) + f.close() + print(f"{rel_path} data has been updated") + else: + print(f"{rel_path} data for the current date was already recorded") + except: + # file is empty or malformed data + error = f"ERROR: {rel_path} file read error" + report_error(error) + if exit_on_save_error: + raise SystemExit(error) + else: + print(error) + else: + # create new file with today's data + all_data = [] + all_data.append(todays_data) + with open(abs_path, 'w') as f: + json.dump(all_data, f, indent=None, separators=(',', ':')) + f.close() + print(f"{rel_path} data has been updated") + +def report_error(error, context=""): + if use_test_data: + return + else: + data = { + # "entry.2112281434": "name", # text + # "entry.1600556346": "option3", # dropdown + # "entry.819260047": ["option2", "option3"], #checkbox multiple + # "entry.1682233942": "option5" # checkbox single + "entry.76518486": error, + "entry.943255668": context + } + try: + requests.post(google_form_error_report_url, data) + print("Error submitted") + except: + error = f"ERROR: {path} file read error" + if exit_on_report_error: + raise SystemExit(error) + else: + print(error) + + +def print_file(rel_path): + # add leading / to relative path if not present + if not rel_path.startswith("/"): + rel_path = "/" + rel_path + abs_path = os.path.dirname(__file__) + rel_path + if os.path.isfile(abs_path): + with open(abs_path, 'r') as f: + contents = json.load(f) + if pretty_print: + pprint(contents) + else: + print(contents) + else: + print("not file") + +def log(data, context=None): + if context: + print(f"{context}:") + if pretty_print: + pp.pprint(data) + else: + print(data) + +def pprint(data): + pp.pprint(data) diff --git a/about.md b/about.md new file mode 100644 index 0000000..9993a18 --- /dev/null +++ b/about.md @@ -0,0 +1,14 @@ +--- +layout: default +title: About +menu_title: About / Contact +permalink: /about +id: about +--- + + +{%- include partials/about/about-state-of-eth.html -%} + +{%- include partials/about/contact.html -%} + +{%- include partials/about/inspiration.html -%} diff --git a/api/documentation.md b/api/documentation.md new file mode 100644 index 0000000..2c8ec49 --- /dev/null +++ b/api/documentation.md @@ -0,0 +1,95 @@ +--- +layout: default +title: API +permalink: /api/v1/documentation +id: apiV1Documentation +--- + + +{% include components/card-alert.html + body="The API is still in alpha and under development so endpoints and responses may change or stop working unexpectedly." + type="danger" +%} + +{% include components/card-alert.html + body="This API is free to use, but please do so responsibly. The data is only updated 1-2 time per day so there's no need to query every minute." + type="info" +%} + +{%- capture content -%} + The API should only be used for testing & non-commercial hobby usage. For serious research and project development work, the data source's native API should be used. + + To use the API, expand the metric of interest and view the endpoint details. +{%- endcapture -%} +{% include components/card-text.html + title="Introduction" + body=content +%} + + + + +{%- capture details -%} + +Consensus Client Diversity + +{% include components/details-api.html + id="consensusclientdiversityvalidators" + title="/metrics/consensus-client-diversity-validators" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators1" + title="/metrics/consensus-client-diversity-nodes" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators2" + title="/metrics/consensus-client-count" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators3" + title="/metrics/consensus-client-languages" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +--- + +Execution Client Diversity + +{% include components/details-api.html + id="consensusclientdiversityvalidators4" + title="/metrics/execution-client-diversity-validators" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators5" + title="/metrics/execution-client-diversity-nodes" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators6" + title="/metrics/execution-client-count" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{% include components/details-api.html + id="consensusclientdiversityvalidators7" + title="/metrics/execution-client-languages" + data=site.data.metrics.consensus-client-diversity-validators.content +%} + +{%- endcapture -%} + + +{% include components/card-text.html + title="Documentation" + body=details +%} + diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..030a810 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,402 @@ +:root { + /*--bs-body-bg: #17181c;*/ + /*--bs-body-bg-rgb: 23, 24, 28;*/ + --bs-body-bg: #0d1217; + --bs-body-bg-rgb: 13, 18, 23; + /*--bs-body-color: #fafafa;*/ + /*--bs-body-color-rgb: rgb(250, 250, 250);*/ + --bs-body-color: #e1e2e3; + --bs-body-color-rgb: rgb(225, 226, 227); + /*--bs-tertiary-bg: #151619; /* used for cards */ + /*--bs-tertiary-bg-rgb: 0, 0, 0; /* used for cards */ + --bs-tertiary-bg: #ffffff05; /* used for cards */ + --bs-tertiary-bg-rgb: 255, 255, 255; /* used for cards */ + --bs-border-radius: 0.5rem; + --bs-border-color: #49505752; + --bs-font-sans-serif: "Inter",system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + --bs-link-color: #2f80ed; + --bs-link-hover-color: #2f80ed; + --bs-link-color-rgb: 47, 128, 237; + --bs-link-hover-color-rgb: 47, 128, 237; + --bs-primary: #2172e5; + --bs-primary-rgb: 33, 114, 229; + + --soe-accent-blue: #2172e5; + --soe-accent-blue-rgb: 33, 114, 229; + --soe-hr-color: #495057; +} +html { + +} +body { + font-size: .875rem; + max-width: 1600px; + margin: 0 auto; +} +main { + min-height: 100vh; + max-height: 100vh; + padding: 1.75rem; + overflow-y: scroll; + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ +} +main::-webkit-scrollbar { + display: none; /* Safari and Chrome */ +} +@media only screen and (max-width: 575px) { + main { + padding: 0.75rem; + } +} +@media only screen and (max-width: 992px) { + main { + padding-top: calc(1.75rem / 2 + 52px) !important; + } +} +aside { + min-height: 100vh; + max-height: 100vh; + /*width: 220px;*/ + /*min-width: 220px;*/ + width: 275px; + min-width: 275px; + /*padding: 1.5rem;*/ + padding: 1.25rem; + padding-right: 0; + overflow-y: scroll; + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ + + font-size: 0.85rem; +} +aside::-webkit-scrollbar { + display: none; /* Safari and Chrome */ +} + +.bg-body-tertiary { + /*--bs-bg-opacity: 0.1;*/ + --bs-bg-opacity: 0.02; +} +.bg-trans { + background-color: transparent; +} + + + +.card { + background-color: var(--bs-tertiary-bg); + /*border: 1px solid rgb(64, 68, 79);*/ + border: 1px solid var(--bs-border-color); + /*margin-bottom: 2.15rem;*/ + margin-bottom: 1.75rem; +} +.card .card { + background-color: #00000040; + margin-bottom: 0.5rem; +} +.card-title, +.card-body h1, +.card-body h2, +.card-body h3, +.card-body h4, +.card-body h5, +.card-body h6 { + font-size: 0.9375rem; + /*font-size: 1.25rem;*/ + padding-top: 0; + padding-bottom: .75rem; + margin-top: 0 !important; + margin-bottom: .75rem; + border-bottom: 1px solid rgba(43, 43, 43, 0.435); +} +.card-body h1, +.card-body h2, +.card-body h3, +.card-body h4, +.card-body h5, +.card-body h6 { + margin-top: 2rem; +} + + + +.btn { + --bs-btn-padding-y: 0.25rem; + /*--bs-btn-padding-x: 0.5rem;*/ + --bs-btn-padding-x: 0.75rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-sm); +} +.btn-group-sm>.btn, .btn-sm { + /*--bs-btn-padding-y: 0.1rem;*/ + --bs-btn-padding-y: 0.05rem; + /*--bs-btn-padding-x: 0.35rem;*/ + --bs-btn-padding-x: 0.5rem; +} +.btn-primary { + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: rgb(var(--soe-accent-blue-rgb)); + --bs-btn-border-color: rgb(var(--soe-accent-blue-rgb)); + --bs-btn-hover-color: var(--bs-body-color); + --bs-btn-active-color: var(--bs-body-color); + --bs-btn-disabled-color: var(--bs-body-color); + --bs-btn-disabled-bg: rgb(var(--soe-accent-blue-rgb)); + --bs-btn-disabled-border-color: rgb(var(--soe-accent-blue-rgb)); +} +btn-check:checked+.btn, +.btn.active, .btn.show, +.btn:first-child:active, +:not(.btn-check)+.btn:active { + border-color: var(--bs-btn-active-border-color) !important; +} + + + +.badge { + --bs-badge-border-radius: 0.25rem; + /*--bs-badge-padding-x: 0.35em;*/ + /*--bs-badge-padding-y: 0.25em;*/ + /*--bs-badge-font-size: 0.8em;*/ + --bs-badge-padding-x: 0.1875rem; + --bs-badge-padding-y: 0.25rem; + --bs-badge-font-size: 0.625rem; + /*--bs-badge-font-weight: 500;*/ + --bs-badge-font-weight: inherit; + /*background-color: rgba(var(--bs-light-rgb),1)!important;*/ + margin-left: 0.35rem; + +} + + + +.alert { + --bs-alert-padding-y: 0.5rem; +} +.alert p:last-child { + padding-bottom: 0; + margin-bottom: 0; +} +.alert > svg, +.alert details summary > svg { + margin-top: 0.15rem; + margin-right: 0.5rem; +} +.alert a { + color: inherit; +} + + +ol, ul { + padding-left: 1rem; +} +li { + margin-bottom: 0.25rem; +} +.card li { + /*width: 20rem;*/ + min-width: 16rem; + max-width: 100% +} + + + + +details summary { + display: flex; + font-weight: 500; + opacity: 1; +} +details summary p { + margin-bottom: 0; +} +details > :not(summary) { + /*padding-left: 1rem;*/ +} +details summary > .arrowicon { + margin-left: auto; + margin-top: -1px; +} +details[open] summary > .arrowicon { + transform: rotate(90deg); + transition: all 0.1s ease 0s; +} +.card details { + margin-top: -0.3rem !important; + margin-bottom: -0.3rem; +} +.card details[open] { + padding-bottom: 0.5rem; +} +details[disabled] summary, +details.disabled summary, +details summary[disabled], +details summary.disabled { + opacity: 0.5 !important; + pointer-events: none !important; + cursor: default !important; + user-select: none; +} + + + + + +.card .table { + --bs-table-bg: var(--bs-body-bg); + margin-bottom: 0; +} +.table>:not(caption)>*>* { + /*padding: 0.625rem 1rem;*/ + padding: 0.5rem 1rem; +} +table tbody tr:last-child { + border-bottom: 0 solid transparent; +} +.table th, +.table td { + border-right: solid 1px; + border-color: inherit; +} +.table th:last-child, +.table td:last-child { + border-right: none; +} +/*adds sticky header*/ +.table-responsive { + max-height: 650px; + overflow: scroll; + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ + position: relative!important; +} +.table-responsive::-webkit-scrollbar { + display: none; +} +@media only screen and (max-width: 575px) { + .table-responsive { + max-height: 400px; + } +} +/*adds sticks first column*/ +.table th:first-child, +.table td:first-child { + position: sticky; + left: 0; +} +table>thead { + /*fallback from .sticky-top*/ + position: sticky; + top: -1px; + z-index: 1020; +} + + + +.input-group-text, +.form-select { + font-size: inherit; +} +select.form-select { + /*background-color: rgb(78 102 137 / 10%);*/ + /*background-color: rgb(138 168 210 / 10%);*/ +} +select.form-select:focus { + border: var(--bs-border-width) solid var(--bs-border-color); + box-shadow: none; +} +.form-control { + font-size: var(--bs-btn-font-size); + background-color: var(--bs-tertiary-bg); +} + + +hr { + color: var(--soe-hr-color); +} + + + +.progress, +.progress-stacked { + --bs-progress-height: 0.5rem; +} +.progress.pending { + opacity: 20%; +} +.progress-bar { + height: 100%; +} +.progress-label { + float: left; + margin-right: 1em; +} +.progress-success { + --progress-success-color: rgba(var(--bs-success-rgb), 15%); + background-image: linear-gradient(45deg,var(--progress-success-color) 25%,transparent 25%,transparent 50%,var(--progress-success-color) 50%,var(--progress-success-color) 75%,transparent 75%,transparent); + background-size: 1rem 1rem; +} +.progress-warning { + --progress-warning-color: rgba(var(--bs-warning-rgb), 15%); + background-image: linear-gradient(45deg,var(--progress-warning-color) 25%,transparent 25%,transparent 50%,var(--progress-warning-color) 50%,var(--progress-warning-color) 75%,transparent 75%,transparent); + background-size: 1rem 1rem; +} +.progress-danger { + --progress-danger-color: rgba(var(--bs-danger-rgb), 15%); + background-image: linear-gradient(45deg,var(--progress-danger-color) 25%,transparent 25%,transparent 50%,var(--progress-danger-color) 50%,var(--progress-danger-color) 75%,transparent 75%,transparent); + background-size: 1rem 1rem; +} +.progress-pending { + --progress-pending-color: rgb(255 255 255 / 38%); + background-image: linear-gradient(45deg,var(--progress-pending-color) 25%,transparent 25%,transparent 50%,var(--progress-pending-color) 50%,var(--progress-pending-color) 75%,transparent 75%,transparent); + background-size: 1rem 1rem; +} + + + +.tooltip { + --bs-tooltip-font-size: 0.8rem; + --bs-tooltip-opacity: 1; + --bs-tooltip-bg: #1d2229; + --bs-tooltip-color: inherit; +} +.text-copy:hover { + cursor: pointer; + opacity: 1; +} + + +code { + color: #c2c2c2; +} +pre { + max-height: 300px; + background-color: var(--bs-tertiary-bg); + border-radius: var(--bs-border-radius); + border: 1px solid; + border-color: var(--bs-border-color); + padding: 1rem; +} +@media only screen and (max-width: 575px) { + pre { + max-height: 200px; + } +} + + + + + + + + + + + + + + + + + + diff --git a/assets/img/health/format-transition-compressed.png b/assets/img/health/format-transition-compressed.png new file mode 100644 index 0000000..ef3658c Binary files /dev/null and b/assets/img/health/format-transition-compressed.png differ diff --git a/assets/img/health/format-transition-old.png b/assets/img/health/format-transition-old.png new file mode 100644 index 0000000..09cad96 Binary files /dev/null and b/assets/img/health/format-transition-old.png differ diff --git a/assets/img/health/format-transition-original.png b/assets/img/health/format-transition-original.png new file mode 100644 index 0000000..60b5ab9 Binary files /dev/null and b/assets/img/health/format-transition-original.png differ diff --git a/assets/img/health/format-transition-resized.png b/assets/img/health/format-transition-resized.png new file mode 100644 index 0000000..160d39d Binary files /dev/null and b/assets/img/health/format-transition-resized.png differ diff --git a/assets/img/health/format-transition.png b/assets/img/health/format-transition.png new file mode 100644 index 0000000..ef3658c Binary files /dev/null and b/assets/img/health/format-transition.png differ diff --git a/assets/img/health/original-narrow.png b/assets/img/health/original-narrow.png new file mode 100644 index 0000000..ada3240 Binary files /dev/null and b/assets/img/health/original-narrow.png differ diff --git a/assets/img/health/original-wide.png b/assets/img/health/original-wide.png new file mode 100644 index 0000000..7f2f8d9 Binary files /dev/null and b/assets/img/health/original-wide.png differ diff --git a/assets/img/logo/logo-lg.png b/assets/img/logo/logo-lg.png new file mode 100644 index 0000000..3a2366a Binary files /dev/null and b/assets/img/logo/logo-lg.png differ diff --git a/assets/img/logo/logo-md.png b/assets/img/logo/logo-md.png new file mode 100644 index 0000000..4eac069 Binary files /dev/null and b/assets/img/logo/logo-md.png differ diff --git a/assets/img/logo/logo-sm.png b/assets/img/logo/logo-sm.png new file mode 100644 index 0000000..2aa2973 Binary files /dev/null and b/assets/img/logo/logo-sm.png differ diff --git a/assets/img/logo/thermometer-original.png b/assets/img/logo/thermometer-original.png new file mode 100644 index 0000000..7d3858e Binary files /dev/null and b/assets/img/logo/thermometer-original.png differ diff --git a/assets/img/logo/thermometer-original.webp b/assets/img/logo/thermometer-original.webp new file mode 100644 index 0000000..4f6c7b3 Binary files /dev/null and b/assets/img/logo/thermometer-original.webp differ diff --git a/assets/img/sponsors/diva.svg b/assets/img/sponsors/diva.svg new file mode 100644 index 0000000..e0735e0 --- /dev/null +++ b/assets/img/sponsors/diva.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/img/sponsors/ef.svg b/assets/img/sponsors/ef.svg new file mode 100644 index 0000000..5586560 --- /dev/null +++ b/assets/img/sponsors/ef.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/sponsors/ethstaker.svg b/assets/img/sponsors/ethstaker.svg new file mode 100644 index 0000000..5cadbf0 --- /dev/null +++ b/assets/img/sponsors/ethstaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/sponsors/staking-foundation.svg b/assets/img/sponsors/staking-foundation.svg new file mode 100644 index 0000000..d649c6e --- /dev/null +++ b/assets/img/sponsors/staking-foundation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/stateofeth/icons/android-chrome-192x192.png b/assets/img/stateofeth/icons/android-chrome-192x192.png new file mode 100644 index 0000000..8a5fb8a Binary files /dev/null and b/assets/img/stateofeth/icons/android-chrome-192x192.png differ diff --git a/assets/img/stateofeth/icons/android-chrome-384x384.png b/assets/img/stateofeth/icons/android-chrome-384x384.png new file mode 100644 index 0000000..8b08934 Binary files /dev/null and b/assets/img/stateofeth/icons/android-chrome-384x384.png differ diff --git a/assets/img/stateofeth/icons/apple-touch-icon.png b/assets/img/stateofeth/icons/apple-touch-icon.png new file mode 100644 index 0000000..5d935cf Binary files /dev/null and b/assets/img/stateofeth/icons/apple-touch-icon.png differ diff --git a/assets/img/stateofeth/icons/browserconfig.xml b/assets/img/stateofeth/icons/browserconfig.xml new file mode 100644 index 0000000..da4d920 --- /dev/null +++ b/assets/img/stateofeth/icons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #17181c + + + diff --git a/assets/img/stateofeth/icons/favicon-16x16.png b/assets/img/stateofeth/icons/favicon-16x16.png new file mode 100644 index 0000000..553ee51 Binary files /dev/null and b/assets/img/stateofeth/icons/favicon-16x16.png differ diff --git a/assets/img/stateofeth/icons/favicon-32x32.png b/assets/img/stateofeth/icons/favicon-32x32.png new file mode 100644 index 0000000..1a43be6 Binary files /dev/null and b/assets/img/stateofeth/icons/favicon-32x32.png differ diff --git a/assets/img/stateofeth/icons/favicon.ico b/assets/img/stateofeth/icons/favicon.ico new file mode 100644 index 0000000..95c6855 Binary files /dev/null and b/assets/img/stateofeth/icons/favicon.ico differ diff --git a/assets/img/stateofeth/icons/favicon_package_v0.16.zip b/assets/img/stateofeth/icons/favicon_package_v0.16.zip new file mode 100644 index 0000000..b45c227 Binary files /dev/null and b/assets/img/stateofeth/icons/favicon_package_v0.16.zip differ diff --git a/assets/img/stateofeth/icons/mstile-150x150.png b/assets/img/stateofeth/icons/mstile-150x150.png new file mode 100644 index 0000000..84182d1 Binary files /dev/null and b/assets/img/stateofeth/icons/mstile-150x150.png differ diff --git a/assets/img/stateofeth/icons/safari-pinned-tab.svg b/assets/img/stateofeth/icons/safari-pinned-tab.svg new file mode 100644 index 0000000..c8f802a --- /dev/null +++ b/assets/img/stateofeth/icons/safari-pinned-tab.svg @@ -0,0 +1,26 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/assets/img/stateofeth/icons/site.webmanifest b/assets/img/stateofeth/icons/site.webmanifest new file mode 100644 index 0000000..2674b7b --- /dev/null +++ b/assets/img/stateofeth/icons/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "State of Eth", + "short_name": "State of Eth", + "icons": [ + { + "src": "/assets/img/stateofeth/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/assets/img/stateofeth/icons/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ], + "theme_color": "#17181c", + "background_color": "#17181c", + "display": "standalone" +} diff --git a/assets/js/dataSourceSelect.js b/assets/js/dataSourceSelect.js new file mode 100644 index 0000000..d3f8e8c --- /dev/null +++ b/assets/js/dataSourceSelect.js @@ -0,0 +1,10 @@ +// change dataset to alternative source +function updateDataSource(select) { + let datasets = document.querySelectorAll(`.dataset`) + datasets.forEach(item => { + item.classList.add("d-none"); + }); + let selectedSource = select.value; + let activeDataset = document.querySelector(`.${selectedSource}`); + activeDataset.classList.remove("d-none"); +} diff --git a/assets/js/historicalCharts.js b/assets/js/historicalCharts.js new file mode 100644 index 0000000..f94fc66 --- /dev/null +++ b/assets/js/historicalCharts.js @@ -0,0 +1,16 @@ +// change data timeframe +function updateDataTimeframe(el, chart, days) { + // hack to remove active class from "all" option since + // the default checked option wasn't highlighting + if (!el.getAttribute("for").includes("-0")) { + let allOption = el.getAttribute("for").split("-")[0] + "-0"; + document.querySelector(`[for=${allOption}]`).classList.remove("active"); + } + // let chart = window[el.parentElement.getAttribute('data-chart')]; + log(chart) + chart.data.labels = chart.data.labels_all.slice(-days); + chart.data.datasets.forEach((data, i) => { + chart.data.datasets[i].data = chart.data.datasets[i].data_all.slice(-days); + }); + chart.update(); +} diff --git a/assets/js/main.js b/assets/js/main.js index ec380ec..ed729e9 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,3 +1,41 @@ --- --- +{% include_relative /dataSourceSelect.js %} +{% include_relative /historicalCharts.js %} +{% include_relative /updateLinkTargets.js %} + + +window.onload = enableTooltips(); + +function enableTooltips() { + // Enable tooltips + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); + const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); +} + +// copy text +const copyBtns = document.getElementsByClassName("text-copy"); +Array.from(copyBtns).forEach(function(element) { + element.addEventListener('click', copyText); +}); +function copyText() { + let copyIconId = this.id; + let textToCopyId = this.getAttribute("data-copy"); + const textToCopy = document.getElementById(textToCopyId).innerText; + // const textToCopy = textToCopyId.setSelectionRange(); + navigator.clipboard.writeText(textToCopy).then(function() { + let tooltipElement = document.getElementById(copyIconId); + let tooltip = bootstrap.Tooltip.getInstance(tooltipElement); + setTimeout(() => { tooltip.hide(); }, 1000); + }, function(err) { + console.error('Async: Could not copy text: ', err); + }); + +} + + + + + + diff --git a/assets/js/updateLinkTargets.js b/assets/js/updateLinkTargets.js new file mode 100644 index 0000000..7d3bf08 --- /dev/null +++ b/assets/js/updateLinkTargets.js @@ -0,0 +1,34 @@ +{%- comment -%} +// - Updates external links and files to open in a new tab +// - Adds an icon indicating the link opens in a new tab +{%- endcomment -%} + +window.onload = updateLinkTargets(); + +// open external links and pdfs in new tab +function updateLinkTargets() { + {%- assign site_url = site.url | split: "//" | last -%} + document.querySelectorAll("a").forEach(link => { + let href = link.href; + // set all links to open in new tab + if (/^(https?:)?\/\//.test(link.href)) { + link.target = "_blank"; + } + // if current domain, use same tab + if (href != undefined && href.includes("{{site_url}}")) { + link.target = "_self"; + } + // if relative links, use new tab + if (href != undefined && !href.includes("https")) { + link.target = "_self"; + } + // open all .pdf, .png, .jpg, .mp4 in new tab + if (/(\.pdf$|\.png$|\.jpe*g$|\.mp4)/.test(href)) { + link.target = "_blank"; + } + // if new-tab class, use new tab + if (link.classList.contains("new-tab")) { + link.target = "_blank"; + } + }) +} diff --git a/dashboards/overview.md b/dashboards/overview.md new file mode 100644 index 0000000..487efd3 --- /dev/null +++ b/dashboards/overview.md @@ -0,0 +1,9 @@ +--- +layout: default +title: Overview +permalink: /dashboards/overview +id: dashboardOverview +--- + + +{%- include components/section-health-dashboard.html -%} diff --git a/dashboards/watchlist.md b/dashboards/watchlist.md new file mode 100644 index 0000000..990a15c --- /dev/null +++ b/dashboards/watchlist.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Watchlist +permalink: /dashboards/watchlist +id: dashboardWatchlist +--- + + +{%- include components/section-health-dashboard.html + dashboard="favorites" +-%} diff --git a/donate.md b/donate.md new file mode 100644 index 0000000..c0cc065 --- /dev/null +++ b/donate.md @@ -0,0 +1,15 @@ +--- +layout: default +title: Donate +permalink: /donate +id: donate +--- + + +{%- include partials/donate/why-donate.html -%} + +{%- include partials/donate/gitcoin.html -%} + +{%- include partials/donate/direct-donations.html -%} + +{%- include partials/donate/use-of-funds.html -%} diff --git a/github.md b/github.md new file mode 100644 index 0000000..debb449 --- /dev/null +++ b/github.md @@ -0,0 +1,8 @@ +--- +layout: default +title: Github +permalink: /github +id: github +redirect_to: https://github.com/etheralpha/stateofeth-com +--- + diff --git a/index.md b/index.md index e6f3f3f..783661f 100644 --- a/index.md +++ b/index.md @@ -1,6 +1,7 @@ --- layout: default +redirect_to: "https://paragraph.xyz/@stateofeth/welcome-to-state-of-eth" --- -{% include partials/content/index.html %} + diff --git a/metrics/consensus-client-diversity-nodes.md b/metrics/consensus-client-diversity-nodes.md new file mode 100644 index 0000000..a597a71 --- /dev/null +++ b/metrics/consensus-client-diversity-nodes.md @@ -0,0 +1,8 @@ +--- +layout: default +title: Consensus Client Diversity (Nodes) +permalink: /metrics/consensus-client-diversity-nodes +--- + + + diff --git a/metrics/consensus-client-diversity-validators.md b/metrics/consensus-client-diversity-validators.md new file mode 100644 index 0000000..3a071c5 --- /dev/null +++ b/metrics/consensus-client-diversity-validators.md @@ -0,0 +1,34 @@ +--- +layout: default +title: Validator Consensus Client Diversity +menu_title: Validator Client Diversity +health_title: Validator Consensus Client Diversity +permalink: /metrics/consensus-client-diversity-validators +# id: consensusClientDiversityValidators +id: consensus-client-diversity-validators +--- + + +{%- include components/section-toc.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include components/section-description.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include components/section-health.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include components/section-data.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include components/section-risks.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include components/section-take-action.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + +{%- include partials/metrics/consensus-client-diversity-validators/client-info.html -%} + +{%- include components/section-resources.html + content=site.data.metrics.consensus-client-diversity-validators.content -%} + diff --git a/newsletter.md b/newsletter.md new file mode 100644 index 0000000..00fd44d --- /dev/null +++ b/newsletter.md @@ -0,0 +1,8 @@ +--- +layout: default +title: Newsletter +permalink: /newsletter +id: newsletter +redirect_to: https://paragraph.xyz/@stateofeth/subscribe +--- + diff --git a/test/contact.html b/test/contact.html new file mode 100644 index 0000000..008a9a5 --- /dev/null +++ b/test/contact.html @@ -0,0 +1,8 @@ +{%- capture content -%} +Meow +{%- endcapture -%} + +{% include components/card-text.html + title="Contact" + body=content +%} \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..27a759b --- /dev/null +++ b/test/index.html @@ -0,0 +1,7 @@ +--- +layout: default +title: Test +--- + + +{%- include_relative contact.html -%} diff --git a/twitter.md b/twitter.md new file mode 100644 index 0000000..d9311b6 --- /dev/null +++ b/twitter.md @@ -0,0 +1,8 @@ +--- +layout: default +title: Twitter +permalink: /twitter +id: twitter +redirect_to: https://twitter.com/hanni_abu +--- +