From aa09c705e271fa425e5d009dd771795d9cf81e41 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:36:44 -0800 Subject: [PATCH 1/6] feat: add solo-staking quiz --- public/content/staking/solo/index.md | 2 + src/data/quizzes/index.ts | 9 ++ src/data/quizzes/questionBank.ts | 209 +++++++++++++++++++++++++++ src/intl/en/learn-quizzes.json | 72 ++++++++- src/lib/utils/translations.ts | 1 + 5 files changed, 292 insertions(+), 1 deletion(-) diff --git a/public/content/staking/solo/index.md b/public/content/staking/solo/index.md index a5d252b5334..5a2c60c055e 100644 --- a/public/content/staking/solo/index.md +++ b/public/content/staking/solo/index.md @@ -202,3 +202,5 @@ To unlock and receive your entire balance back you must also complete the proces - [How To: Shop For Ethereum Validator Hardware](https://www.youtube.com/watch?v=C2wwu1IlhDc) - _EthStaker 2022_ - [Step by Step: How to join the Ethereum 2.0 Testnet](https://kb.beaconcha.in/guides/tutorial-eth2-multiclient) - _Butta_ - [Eth2 Slashing Prevention Tips](https://medium.com/prysmatic-labs/eth2-slashing-prevention-tips-f6faa5025f50) - _Raul Jordan 2020_ + + diff --git a/src/data/quizzes/index.ts b/src/data/quizzes/index.ts index cb22f4236f5..0e42f2206f1 100644 --- a/src/data/quizzes/index.ts +++ b/src/data/quizzes/index.ts @@ -36,6 +36,10 @@ const quizzes: RawQuizzes = { title: "learn-quizzes:page-assets-merge", questions: ["h001", "h002", "h003", "h004", "h005"], }, + "solo-staking": { + title: "solo", + questions: ["j001", "j002", "j004", "j005", "j006", "j007", "j008"], + }, } export const ethereumBasicsQuizzes: QuizzesSection[] = [ @@ -79,6 +83,11 @@ export const usingEthereumQuizzes: QuizzesSection[] = [ { id: "layer-2", level: "intermediate", + next: "solo-staking", + }, + { + id: "solo-staking", + level: "advanced", }, ] diff --git a/src/data/quizzes/questionBank.ts b/src/data/quizzes/questionBank.ts index ba3d3907753..77fac3de7e5 100644 --- a/src/data/quizzes/questionBank.ts +++ b/src/data/quizzes/questionBank.ts @@ -887,6 +887,215 @@ const questionBank: QuestionBank = { ], correctAnswerId: "h005-b", }, + // Solo staking + j001: { + prompt: "j001-prompt", + answers: [ + { + id: "j001-a", + label: "j001-a-label", + explanation: "j001-a-explanation", + }, + { + id: "j001-b", + label: "j001-b-label", + explanation: "j001-b-explanation", + }, + { + id: "j001-c", + label: "j001-c-label", + explanation: "j001-c-explanation", + }, + { + id: "j001-d", + label: "j001-d-label", + explanation: "j001-d-explanation", + }, + ], + correctAnswerId: "j001-d", + }, + j002: { + prompt: "j002-prompt", + answers: [ + { + id: "j002-a", + label: "j002-a-label", + explanation: "j002-a-explanation", + }, + { + id: "j002-b", + label: "j002-b-label", + explanation: "j002-b-explanation", + }, + { + id: "j002-c", + label: "j002-c-label", + explanation: "j002-c-explanation", + }, + { + id: "j002-d", + label: "j002-d-label", + explanation: "j002-d-explanation", + }, + ], + correctAnswerId: "j002-b", + }, + j003: { + prompt: "j003-prompt", + answers: [ + { + id: "j003-a", + label: "j003-a-label", + explanation: "j003-a-explanation", + }, + { + id: "j003-b", + label: "j003-b-label", + explanation: "j003-b-explanation", + }, + { + id: "j003-c", + label: "j003-c-label", + explanation: "j003-c-explanation", + }, + { + id: "j003-d", + label: "j003-d-label", + explanation: "j003-d-explanation", + }, + ], + correctAnswerId: "j003-b", + }, + j004: { + prompt: "j004-prompt", + answers: [ + { + id: "j004-a", + label: "j004-a-label", + explanation: "j004-a-explanation", + }, + { + id: "j004-b", + label: "j004-b-label", + explanation: "j004-b-explanation", + }, + { + id: "j004-c", + label: "j004-c-label", + explanation: "j004-c-explanation", + }, + { + id: "j004-d", + label: "j004-d-label", + explanation: "j004-d-explanation", + }, + ], + correctAnswerId: "j004-d", + }, + j005: { + prompt: "j005-prompt", + answers: [ + { + id: "j005-a", + label: "j005-a-label", + explanation: "j005-a-explanation", + }, + { + id: "j005-b", + label: "j005-b-label", + explanation: "j005-b-explanation", + }, + { + id: "j005-c", + label: "j005-c-label", + explanation: "j005-c-explanation", + }, + { + id: "j005-d", + label: "j005-d-label", + explanation: "j005-d-explanation", + }, + ], + correctAnswerId: "j005-c", + }, + j006: { + prompt: "j006-prompt", + answers: [ + { + id: "j006-a", + label: "j006-a-label", + explanation: "j006-a-explanation", + }, + { + id: "j006-b", + label: "j006-b-label", + explanation: "j006-b-explanation", + }, + { + id: "j006-c", + label: "j006-c-label", + explanation: "j006-b-explanation", + }, + { + id: "j006-d", + label: "j006-d-label", + explanation: "j006-b-explanation", + }, + ], + correctAnswerId: "j006-a", + }, + j007: { + prompt: "j007-prompt", + answers: [ + { + id: "j007-a", + label: "j007-a-label", + explanation: "j007-a-explanation", + }, + { + id: "j007-b", + label: "j007-b-label", + explanation: "j007-b-explanation", + }, + { + id: "j007-c", + label: "j007-c-label", + explanation: "j007-c-explanation", + }, + { + id: "j007-d", + label: "j007-d-label", + explanation: "j007-d-explanation", + }, + ], + correctAnswerId: "j007-c", + }, + j008: { + prompt: "j008-prompt", + answers: [ + { + id: "j008-a", + label: "j008-a-label", + explanation: "j008-a-explanation", + }, + { + id: "j008-b", + label: "j008-b-label", + explanation: "j008-b-explanation", + }, + { + id: "j008-c", + label: "j008-c-label", + explanation: "j008-c-explanation", + }, + { + id: "j008-d", + label: "j008-d-label", + explanation: "j008-d-explanation", + }, + ], + correctAnswerId: "j008-d", + }, } export default questionBank diff --git a/src/intl/en/learn-quizzes.json b/src/intl/en/learn-quizzes.json index c62ecbb2a97..10a34ed6494 100644 --- a/src/intl/en/learn-quizzes.json +++ b/src/intl/en/learn-quizzes.json @@ -325,5 +325,75 @@ "h005-c-label": "Eth1", "h005-c-explanation": "Eth1 was the original name given to the execution layer, not the consensus layer.", "h005-d-label": "Staking", - "h005-d-explanation": "Staking is depositing ETH into a smart contract to help secure the chain." + "h005-d-explanation": "Staking is depositing ETH into a smart contract to help secure the chain.", + "j001-prompt": "Which is true about slashing?", + "j001-a-label": "Penalty for being offline, rewards resume when back online", + "j001-a-explanation": "Being offline does NOT result in slashing. Small penalties are incurred for being offline, and rewards resume when the validator returns online and resumes attestations.", + "j001-b-label": "Penalty for being offline, validator is immediately prohibited from attesting ever again", + "j001-b-explanation": "Being offline does NOT result in slashing. While slashing will result in the validator being prohibited from ever attesting again and is ultimately forcefully ejected, being offline will NOT result in ejection from the network.", + "j001-c-label": "Penalty for breaking specific consensus rules, rewards resume after slashing", + "j001-c-explanation": "Slashing is a serious penalty for breaking specific consensus rules that present a threat to the network. As such, once a validator is slashed they are immediately prohibited from attesting any further, and are ultimately forcefully ejected from the network and remaining ETH is withdrawn to the owner.", + "j001-d-label": "Penalty for breaking specific consensus rules, validator is immediately prohibited from attesting ever again", + "j001-d-explanation": "Slashing is a serious penalty for breaking specific consensus rules that present a threat to the network. As such, once a validator is slashed they are immediately prohibited from attesting any further, and are ultimately forcefully ejected from the network and remaining ETH is withdrawn to the owner.", + "j002-prompt": "What happens if a validator goes offline?", + "j002-a-label": "No affect on rewards", + "j002-a-explanation": "Penalties are incurred when a validator is unavailable to attest to the state of the chain for any given epoch. The size of these penalties is approximately equal to 75% of what the reward for a proper attestation would have been. Rewards resume when validator goes back online, and NO slashing occurs.", + "j002-b-label": "Inactivity penalties are incurred only while unavailable", + "j002-b-explanation": "While unavailable, a validator will incur small inactivity penalties, approximately equal to 75% of what the reward would have been for a proper attestation. In rare/extreme cases where the network is not finalizing (i.e. over 1/3 of the network is also offline), these penalties are significantly greater. Rewards resume when validator goes back online, and no slashing occurs.", + "j002-c-label": "Immediate slashing and removal from the network", + "j002-c-explanation": "This is a common misconception, but going offline does NOT result in slashing! Slashing is a specific type of penalty for more serious offense, with larger penalties and also results in removal from the validator set.", + "j002-d-label": "One week delay before slashing and ejection", + "j002-d-explanation": "Being offline does NOT result in slashing, even after extended period of time. A validator could theoretically be offline for years without being slashed, though inactivity penalties would mount if the validator does not exit.", + "j003-prompt": "What is the max effective balance of a validator?", + "j003-a-label": "16", + "j003-a-explanation": "Validators who drop to an effective balance of 16 ETH are automatically exited from the Beacon Chain.", + "j003-b-label": "32", + "j003-b-explanation": "32 ETH is both the minimum ETH required to activate a new validator, and also the maximum 'effective balance' (vote weight) for that validator. Rewards above 32 can be accrued, but this balance does not contribute to the weight of that validators vote on the network and rewards are not increased.", + "j003-c-label": "Variable depending on the operator", + "j003-c-explanation": "The rules of consensus apply to every validator account equally and are not dependent upon the individual operating the node. The max effective balance of all validators is 32 ETH.", + "j003-d-label": "No limit", + "j003-d-explanation": "Each validator account is limited to an effective balance of 32 ETH, limiting the overall power of any single validator on the network. This also limits how much ETH can be staked or un-staked in a given time period, as validator activations and exits are processed through a rate-limited queue.", + "j004-prompt": "Which is NOT a reward received as a validator?", + "j004-a-label": "Block reward", + "j004-a-explanation": "Validators receive rewards in the form of new ETH issuance for proposing a valid block when randomly selected by the protocol. These rewards are separate from the fees and MEV that are also earned when proposing blocks.", + "j004-b-label": "Fee tips / MEV", + "j004-b-explanation": "Fee tips (unburnt portion of fees) and MEV earnings are distributed to the block proposer (staker/validator) via the fee recipient address provided by that validator. These rewards are separate from the block reward also earned when proposing blocks.", + "j004-c-label": "Head of chain attestation reward", + "j004-c-explanation": "Validators receive rewards in the form of new ETH issuance for correctly and promptly attesting to the head of the chain, the current justified epoch head, and the current finalized epoch head.", + "j004-d-label": "Uniswap trading fees", + "j004-d-explanation": "Trading fees generated by trading platforms and exchanges are not received by Ethereum validators.", + "j005-prompt": "What uptime is required for a validator to be profitable?", + "j005-a-label": "100%", + "j005-a-explanation": "Although an ideal target, achieving 100% uptime is not the minimum requirement for a validator to remain profitable.", + "j005-b-label": "~99%", + "j005-b-explanation": "Although an excellent target, achieving 99% uptime is not the minimum requirement for a validator to remain profitable.", + "j005-c-label": "~50%", + "j005-c-explanation": "Validators are penalized approximately 75% of what they would have been rewarded for correctly and promptly attesting to the state of the chain. This means for a given time period, being offline 50% of that time will still be net profitable, albeit less profitable than a more reliably available validator.", + "j005-d-label": "~25%", + "j005-d-explanation": "A validator with only 25% uptime will be incurring penalties for the other 75% of the time. Given the similar size of rewards and penalties, being offline for 3x the amount of time online will result in a net loss of ETH for that time period.", + "j006-prompt": "Which of the following is NOT a slashable offense?", + "j006-a-label": "Being offline", + "j006-a-explanation": "Simply being offline does not result in slashing. It will result in small inactivity penalties while offline, but will resume attesting when back online.", + "j006-b-label": "Proposing and signing two different blocks for the same slot", + "j006-b-explanation": "This threatens the integrity of the network and will result in slashing and ejection from the network.", + "j006-c-label": "Attesting to a block that 'surrounds' another one (effectively changing history)", + "j006-d-label": "'Double voting' by attesting to two candidates for the same block", + "j007-prompt": "Which is NOT a way to protect/prevent your validator from being slashed?", + "j007-a-label": "Avoid overly redundant setups, and only store your keys with one validator client at a time", + "j007-a-explanation": "The majority of slashing to date are from operators storing their signing keys on more than one machine, as a redundant backup. This is highly risky, as any malfunction can result in double voting and slashing.", + "j007-b-label": "Run client software as-is without altering the code yourself", + "j007-b-explanation": "Client software is written and tested to protect against performing slashable actions. To execute a slashable action, this would typically require altering the client code yourself in a malicious manner.", + "j007-c-label": "Run a client that is being used by the majority of other validators", + "j007-c-explanation": "Using the same client as a majority of the rest of the network puts you at risk for being slashed in the event of a software bug in that client. Running a minority client protects against this.", + "j007-d-label": "Disable validator for 2-4 epochs before migrating keys to a new machine", + "j007-d-explanation": "This allows time to allow the chain to finalize while your node is offline, to minimize any risk of accidental double voting and slashing during key migration.", + "j008-prompt": "Which is NOT required to receive reward payments / partial withdrawals?", + "j008-a-label": "Providing an execution withdrawal address one time", + "j008-a-explanation": "This is required once for the withdrawal process to know where to send any consensus layer funds to", + "j008-b-label": "Having an effective balance of 32 ETH", + "j008-b-explanation": "Your effective balance must be maxed out at 32 ETH before any partial withdrawals will trigger.", + "j008-c-label": "Having a total balance over 32 ETH", + "j008-c-explanation": "Your total balance must have rewards above 32 ETH for any partial withdrawals to trigger.", + "j008-d-label": "Submitting requested withdrawal amount with gas payment", + "j008-d-explanation": "Once the other criteria are met, reward payments are automatic. Recipients do not need to submit a transaction or pay gas. Amount withdrawn is equal to validator's balance in excess of 32. Custom amounts cannot be requested." } diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts index d0f54849c6b..6aa97f961c8 100644 --- a/src/lib/utils/translations.ts +++ b/src/lib/utils/translations.ts @@ -189,6 +189,7 @@ const getRequiredNamespacesForPath = (path: string) => { path.startsWith("/nft") || path.startsWith("/roadmap/merge") || path.startsWith("/security") || + path.startsWith("/staking/solo") || path.startsWith("/wallets") || path.startsWith("/web3") || path.startsWith("/what-is-ethereum") || From 52ed9db199ff3fd341c3d8dd88fead733415215d Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:43:10 -0800 Subject: [PATCH 2/6] feat: add scaling quiz --- public/content/roadmap/scaling/index.md | 2 + src/data/quizzes/index.ts | 9 ++ src/data/quizzes/questionBank.ts | 105 ++++++++++++++++++++++++ src/intl/en/learn-quizzes.json | 38 ++++++++- src/lib/utils/translations.ts | 1 + 5 files changed, 154 insertions(+), 1 deletion(-) diff --git a/public/content/roadmap/scaling/index.md b/public/content/roadmap/scaling/index.md index d2fad35d983..f03957e830e 100644 --- a/public/content/roadmap/scaling/index.md +++ b/public/content/roadmap/scaling/index.md @@ -45,3 +45,5 @@ This second step is known as [“Danksharding”](/roadmap/danksharding/). It is ## Current progress {#current-progress} Proto-Danksharding is likely to be one of the earlier roadmap items to be implemented. The decentralized computation steps required to set it up are already underway and several clients have implemented prototypes for handling blob data. Full Danksharding is likely several years away, as it relies upon several other roadmap items being completed first. Decentralizing rollup infrastructure is likely to be a gradual process - there are many different rollups that are building slightly different systems and will fully decentralize at different rates. + + diff --git a/src/data/quizzes/index.ts b/src/data/quizzes/index.ts index 0e42f2206f1..a9ce0b41446 100644 --- a/src/data/quizzes/index.ts +++ b/src/data/quizzes/index.ts @@ -40,6 +40,10 @@ const quizzes: RawQuizzes = { title: "solo", questions: ["j001", "j002", "j004", "j005", "j006", "j007", "j008"], }, + scaling: { + title: "scaling", + questions: ["k001", "k002", "k003", "k004"], + }, } export const ethereumBasicsQuizzes: QuizzesSection[] = [ @@ -78,6 +82,11 @@ export const usingEthereumQuizzes: QuizzesSection[] = [ { id: "nfts", level: "beginner", + next: "scaling", + }, + { + id: "scaling", + level: "intermediate", next: "layer-2", }, { diff --git a/src/data/quizzes/questionBank.ts b/src/data/quizzes/questionBank.ts index 77fac3de7e5..03a524957e1 100644 --- a/src/data/quizzes/questionBank.ts +++ b/src/data/quizzes/questionBank.ts @@ -1096,6 +1096,111 @@ const questionBank: QuestionBank = { ], correctAnswerId: "j008-d", }, + // Scaling + k001: { + prompt: "k001-prompt", + answers: [ + { + id: "k001-a", + label: "k001-a-label", + explanation: "k001-a-explanation", + }, + { + id: "k001-b", + label: "k001-b-label", + explanation: "k001-b-explanation", + }, + { + id: "k001-c", + label: "k001-c-label", + explanation: "k001-c-explanation", + }, + { + id: "k001-d", + label: "k001-d-label", + explanation: "k001-d-explanation", + }, + ], + correctAnswerId: "k001-d", + }, + k002: { + prompt: "k002-prompt", + answers: [ + { + id: "k002-a", + label: "k002-a-label", + explanation: "k002-a-explanation", + }, + { + id: "k002-b", + label: "k002-b-label", + explanation: "k002-b-explanation", + }, + { + id: "k002-c", + label: "k002-c-label", + explanation: "k002-c-explanation", + }, + { + id: "k002-d", + label: "k002-d-label", + explanation: "k002-d-explanation", + }, + ], + correctAnswerId: "k002-c", + }, + k003: { + prompt: "k003-prompt", + answers: [ + { + id: "k003-a", + label: "k003-a-label", + explanation: "k003-a-explanation", + }, + { + id: "k003-b", + label: "k003-b-label", + explanation: "k003-b-explanation", + }, + { + id: "k003-c", + label: "k003-c-label", + explanation: "k003-c-explanation", + }, + { + id: "k003-d", + label: "k003-d-label", + explanation: "k003-d-explanation", + }, + ], + correctAnswerId: "k003-d", + }, + k004: { + prompt: "k004-prompt", + answers: [ + { + id: "k004-a", + label: "k004-a-label", + explanation: "k004-a-explanation", + }, + { + id: "k004-b", + label: "k004-b-label", + explanation: "k004-b-explanation", + }, + { + id: "k004-c", + label: "k004-c-label", + explanation: "k004-c-explanation", + }, + { + id: "k004-d", + label: "k004-d-label", + explanation: "k004-d-explanation", + }, + ], + correctAnswerId: "k004-b", + } } export default questionBank diff --git a/src/intl/en/learn-quizzes.json b/src/intl/en/learn-quizzes.json index 10a34ed6494..ce216f65065 100644 --- a/src/intl/en/learn-quizzes.json +++ b/src/intl/en/learn-quizzes.json @@ -395,5 +395,41 @@ "j008-c-label": "Having a total balance over 32 ETH", "j008-c-explanation": "Your total balance must have rewards above 32 ETH for any partial withdrawals to trigger.", "j008-d-label": "Submitting requested withdrawal amount with gas payment", - "j008-d-explanation": "Once the other criteria are met, reward payments are automatic. Recipients do not need to submit a transaction or pay gas. Amount withdrawn is equal to validator's balance in excess of 32. Custom amounts cannot be requested." + "j008-d-explanation": "Once the other criteria are met, reward payments are automatic. Recipients do not need to submit a transaction or pay gas. Amount withdrawn is equal to validator's balance in excess of 32. Custom amounts cannot be requested.", + "k001-prompt": "Which of the following is Ethereum using to scale?", + "k001-a-label": "Layer 2 rollups", + "k001-a-explanation": "These help Ethereum scale by bundling transactions, executing them, and then posting the results to Ethereum for validation and securing. Examples or rollups include Arbitrum or Optimism. This is not the only way Ethereum is scaling.", + "k001-b-label": "Proto-Danksharding", + "k001-b-explanation": "This provides a temporary and inexpensive storage option for saving rollup data to Mainnet, which currently is responsible for approximately 90% of the cost a user encounters on a rollup. This is not the only way Ethereum is scaling.", + "k001-c-label": "Danksharding", + "k001-c-explanation": "This removes the need for every validator and node on the network from being required to store 100% of the data for all rollups, reducing hardware requirements for node operators. This is not the only way Ethereum is scaling.", + "k001-d-label": "All of the above", + "k001-d-explanation": "Layer 2 rollups bundle transactions, Proto-Danksharding creates cheap temporary storage for this data, and Danksharding shares the storage burden across all validators—all helping Ethereum scale.", + "k002-prompt": "After bundling transactions and executing them, what do layer 2 rollups do next?", + "k002-a-label": "Store the data on a private server", + "k002-a-explanation": "Results are posted to Mainnet for transparency and public availability, and are not reliant on private servers.", + "k002-b-label": "Sends the proof to the user for storage", + "k002-b-explanation": "Users are not expected to hold onto the results of their transaction. This information is posted to Mainnet.", + "k002-c-label": "Submit the results to Ethereum", + "k002-c-explanation": "Layer 2 rollups post the results of their transaction execution to Mainnet, securing it in Ethereum's history", + "k002-d-label": "Delete the result to reduce costs", + "k002-d-explanation": "Layer 2 rollups post the results of their transaction execution to Mainnet. The cost savings achieved with this approach is by bundling and compressing transaction data, and ultimately storing it in cheap storage that expires once made available to those who need it.", + "k003-prompt": "How does Proto-Danksharding reduce rollup transaction costs on rollups?", + "k003-a-label": "Directly increasing the block size", + "k003-a-explanation": "Proto-Danksharding does not directly increase the gas limit, but does make the storage of rollup data less expensive by making temporary storage available", + "k003-b-label": "Splitting up which validators are required to store the data", + "k003-b-explanation": "Although full Danksharding is expected to reduce the need for all validators to store all of the data, this is preceded by Proto-Danksharding which forms a less-expensive, temporary storage option for the data produced by rollups.", + "k003-c-label": "Significantly increasing hardware requirements for node operators", + "k003-c-explanation": "This is generally not considered an acceptable option for scaling Ethereum. Great efforts are taken to minimize hardware requirements for operating a node to keep it as accessible as possible.", + "k003-d-label": "Storing its data in cheaper, temporary 'blob' storage", + "k003-d-explanation": "Proto-Danksharding introduces a temporary data storage option for rollups to allow them to more cheaply post its results to Mainnet", + "k004-prompt": "What is a critical next step for rollups to scale Ethereum?", + "k004-a-label": "Incentivize entities with powerful computers to handle all of the sequencing", + "k004-a-explanation": "One of the problems with current rollups is the centralized nature of those running the sequencers (those who decide the inclusion and ordering of transactions within a rollup). The goal is to allow anyone to participate, and not rely on a single group or entity in any way.", + "k004-b-label": "Distribute responsibility for running sequencers and provers across more people", + "k004-b-explanation": "Control over a rollup typically starts off centralized, which helps get things started, but leaves the network prone to censorship. Decentralizing the process of including transactions so anyone can participate is essential to prevent the possibility of network compromise.", + "k004-c-label": "Make all rollups conform to the same method of security", + "k004-c-explanation": "Ethereum benefits from having a wide range of approaches to security within its rollup ecosystem as a form of resiliency.", + "k004-d-label": "Data oracles to confirm storage of transaction data on private servers", + "k004-d-explanation": "Rollup data is stored on Ethereum, and does not rely on private servers or databases." } diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts index 6aa97f961c8..7229d2d08dc 100644 --- a/src/lib/utils/translations.ts +++ b/src/lib/utils/translations.ts @@ -188,6 +188,7 @@ const getRequiredNamespacesForPath = (path: string) => { path.startsWith("/layer-2") || path.startsWith("/nft") || path.startsWith("/roadmap/merge") || + path.startsWith("/roadmap/scaling") || path.startsWith("/security") || path.startsWith("/staking/solo") || path.startsWith("/wallets") || From d048d70ad2e321dce1c6f7b577d121d05751597d Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:53:58 -0800 Subject: [PATCH 3/6] feat: add run-a-node quiz --- src/data/quizzes/index.ts | 9 ++ src/data/quizzes/questionBank.ts | 149 ++++++++++++++++++++++++++++++- src/intl/en/learn-quizzes.json | 51 ++++++++++- src/lib/utils/translations.ts | 3 +- src/pages/run-a-node.tsx | 4 +- 5 files changed, 211 insertions(+), 5 deletions(-) diff --git a/src/data/quizzes/index.ts b/src/data/quizzes/index.ts index a9ce0b41446..740d79a5d8b 100644 --- a/src/data/quizzes/index.ts +++ b/src/data/quizzes/index.ts @@ -44,6 +44,10 @@ const quizzes: RawQuizzes = { title: "scaling", questions: ["k001", "k002", "k003", "k004"], }, + "run-a-node": { + title: "run-a-node", + questions: ["l001", "l002", "l003", "l004", "l005", "l006"], + }, } export const ethereumBasicsQuizzes: QuizzesSection[] = [ @@ -92,6 +96,11 @@ export const usingEthereumQuizzes: QuizzesSection[] = [ { id: "layer-2", level: "intermediate", + next: "run-a-node", + }, + { + id: "run-a-node", + level: "intermediate", next: "solo-staking", }, { diff --git a/src/data/quizzes/questionBank.ts b/src/data/quizzes/questionBank.ts index 03a524957e1..2a63a969fe2 100644 --- a/src/data/quizzes/questionBank.ts +++ b/src/data/quizzes/questionBank.ts @@ -1200,7 +1200,154 @@ const questionBank: QuestionBank = { }, ], correctAnswerId: "k004-b", - } + }, + // Run a node + l001: { + prompt: "l001-prompt", + answers: [ + { + id: "l001-a", + label: "l001-a-label", + explanation: "l001-a-explanation", + }, + { + id: "l001-b", + label: "l001-b-label", + explanation: "l001-b-explanation", + }, + { + id: "l001-c", + label: "l001-c-label", + explanation: "l001-c-explanation", + }, + { + id: "l001-d", + label: "l001-d-label", + explanation: "l001-d-explanation", + }, + ], + correctAnswerId: "l001-a", + }, + l002: { + prompt: "l002-prompt", + answers: [ + { + id: "l002-a", + label: "l002-a-label", + explanation: "l002-a-explanation", + }, + { + id: "l002-b", + label: "l002-b-label", + explanation: "l002-a-explanation", + }, + { + id: "l002-c", + label: "l002-c-label", + explanation: "l002-a-explanation", + }, + { + id: "l002-d", + label: "l002-d-label", + explanation: "l002-d-explanation", + }, + ], + correctAnswerId: "l002-a", + }, + l003: { + prompt: "l003-prompt", + answers: [ + { + id: "l003-a", + label: "l003-a-label", + explanation: "l003-a-explanation", + }, + { + id: "l003-b", + label: "l003-b-label", + explanation: "l003-b-explanation", + }, + { + id: "l003-c", + label: "l003-c-label", + explanation: "l003-c-explanation", + }, + { + id: "l003-d", + label: "l003-d-label", + explanation: "l003-d-explanation", + }, + ], + correctAnswerId: "l003-d", + }, + l004: { + prompt: "l004-prompt", + answers: [ + { + id: "l004-a", + label: "l004-a-label", + explanation: "l004-a-explanation", + }, + { + id: "l004-b", + label: "l004-b-label", + explanation: "l004-b-explanation", + }, + { + id: "l004-c", + label: "l004-c-label", + explanation: "l004-c-explanation", + }, + { + id: "l004-d", + label: "l004-d-label", + explanation: "l004-d-explanation", + }, + ], + correctAnswerId: "l004-c", + }, + l005: { + prompt: "l005-prompt", + answers: [ + { + id: "l005-a", + label: "l005-a-label", + explanation: "l005-a-explanation", + }, + { + id: "l005-b", + label: "l005-b-label", + explanation: "l005-b-explanation", + }, + { + id: "l005-c", + label: "l005-c-label", + explanation: "l005-c-explanation", + }, + { + id: "l005-d", + label: "l005-d-label", + explanation: "l005-d-explanation", + }, + ], + correctAnswerId: "l005-a", + }, + l006: { + prompt: "l006-prompt", + answers: [ + { + id: "l006-a", + label: "l006-a-label", + explanation: "l006-a-explanation", + }, + { + id: "l006-b", + label: "l006-b-label", + explanation: "l006-a-explanation", + }, + ], + correctAnswerId: "l006-b", + }, } export default questionBank diff --git a/src/intl/en/learn-quizzes.json b/src/intl/en/learn-quizzes.json index ce216f65065..6b61b470f0f 100644 --- a/src/intl/en/learn-quizzes.json +++ b/src/intl/en/learn-quizzes.json @@ -431,5 +431,52 @@ "k004-c-label": "Make all rollups conform to the same method of security", "k004-c-explanation": "Ethereum benefits from having a wide range of approaches to security within its rollup ecosystem as a form of resiliency.", "k004-d-label": "Data oracles to confirm storage of transaction data on private servers", - "k004-d-explanation": "Rollup data is stored on Ethereum, and does not rely on private servers or databases." -} + "k004-d-explanation": "Rollup data is stored on Ethereum, and does not rely on private servers or databases.", + "l001-prompt": "What is required to run a node?", + "l001-a-label": "Running client software with modest hardware while staying online.", + "l001-a-explanation": "Operating a node consists of running software that communicates using the language of the Ethereum protocol with other computers doing the same. This software downloads a copy of the Ethereum blockchain, verifies the validity of every block, then keeps it up-to-date with new blocks and transactions, while helping others download and update their own copies.", + "l001-b-label": "Deposit 32 ETH to earn rewards", + "l001-b-explanation": "This is a requirement for staking—the process of becoming an active participant in network consensus. This is not required to simply run a sovereign copy of the blockchain, which requires NO ETH.", + "l001-c-label": "Operate powerful ASIC mining machines to reach network consensus", + "l001-c-explanation": "Although Ethereum previously used mining with powerful computers to reach consensus, this process has been replaced entirely by staking. Neither mining in the past, nor staking currently, are required to simply operate a sovereign copy of the blockchain.", + "l001-d-label": "Work full time in blockchain infrastructure", + "l001-d-explanation": "Software tooling has continued to improve over time making running a node from home as a novice much more approachable. Working full-time in blockchain infrastructure is by no means a requirement to get involved.", + "l002-prompt": "How much ETH do you need to stake to run a node?", + "l002-a-label": "0", + "l002-a-explanation": "Operating an Ethereum node does not require any ETH. In contrast to operating a staking validator as part of a node setup, anyone is free to run client software and sync their own sovereign copy of the blockchain—no ETH required.", + "l002-b-label": "8", + "l002-c-label": "16", + "l002-d-label": "32", + "l002-d-explanation": "Operating an Ethereum node does not require any ETH. In contrast to the 32 ETH required to activate a staking validator that participates directly in network consensus, anyone is free to run client software and sync their own sovereign copy of the blockchain—no ETH required.", + "l003-prompt": "What benefits do you get from running your own node?", + "l003-a-label": "Censorship resistance", + "l003-a-explanation": "This is a benefit to users, but is not the only one. By running node software that communicates directly with other peers on the network, your transactions get mixed in with every other transaction your node is propagating. As such, it's nearly impossible to differentiate and censor a valid transaction that your node has shared.", + "l003-b-label": "Sovereignty", + "l003-b-explanation": "This is a benefit to users, but not the only one. By having your own copy of the Ethereum blockchain, you no longer depend on any single external party to interact with the network. You never have to ask permission to look up your balance, or to execute a transaction, and all transactions are verified using software you're running yourself. When network upgrades occur, you're in charge of whether to support the upgrade or not.", + "l003-c-label": "Privacy", + "l003-c-explanation": "This is a benefit to users, but is not the only one. Without your own node, simply looking up your account balances typically requires sending a list of your accounts from your wallet, attached to your IP address, to a third-party provider who then is being trusted to provide you with the correct information.", + "l003-d-label": "All of the above", + "l003-d-explanation": "Running a node gives you full control and sovereignty over the data you're relying on, allowing you to privately view and verify the contents of the chain, and effectively guarantee that any valid transactions are not censored.", + "l004-prompt": "What hard drive storage is required for an Ethereum node?", + "l004-a-label": "512 GB SSD", + "l004-a-explanation": "Currently, no client software is capable of storing the chain using only 512 GB", + "l004-b-label": "2 TB Rotating", + "l004-b-explanation": "Generally speaking, rotating hard disks do not support the read/write speeds needed to keep up with processing requirements for an Ethereum node, and an SSD drive is recommended", + "l004-c-label": "2 TB SSD", + "l004-c-explanation": "At time of writing, a 2 TB SSD drive should satisfy the storage and read/write speed requirements for a full Ethereum node.", + "l004-d-label": "8 TB SSD", + "l004-d-explanation": "At time of writing, a 2 TB SSD drive should satisfy the storage and read/write speed requirements for a full Ethereum node. An 8 TB SSD would allow for more future-proofing, and the ability to also sync layer 2 chains, but is not currently a requirement for Mainnet.", + "l005-prompt": "What happens if your node goes offline?", + "l005-a-label": "Your node drops out of sync with the current state of the network", + "l005-a-explanation": "When your node is not available online, it is unable to receive new transactions and blocks from peers, and as such falls out-of-sync with the current state of the chain. Connecting back online will allow your node software to get synced back up to be fully functional again.", + "l005-b-label": "The ETH in your cold storage is slashed", + "l005-b-explanation": "ETH held in your cold storage has nothing to do with your node being online or not. If your node is offline, you won't be able to use it to look up the latest balance of your accounts, but being offline does not put your secured funds at risk. If you're also running validator software with your node as a staker, small penalties will be incurred to this validator balance while unavailable to the network.", + "l005-c-label": "The energy used looking for proof-of-work is wasted", + "l005-c-explanation": "Ethereum no longer uses proof-of-work, and this was never a requirement of all node operators. Being offline simply means your node is no longer in sync with the latest changes on the network, and can re-sync by returning online.", + "l005-d-label": "Chain data is removed, and re-syncing from scratch is required", + "l005-d-explanation": "Simply going offline does not typically delete any saved chain data. Connecting back to the internet will allow the software to resume where it left off to sync up with the latest transactions.", + "l006-prompt": "Running a node earns network rewards", + "l006-a-label": "True", + "l006-a-explanation": "Simply running client software does not earn you rewards. To earn rewards, you must also be staking.", + "l006-b-label": "False" +} \ No newline at end of file diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts index 7229d2d08dc..f9179a68c55 100644 --- a/src/lib/utils/translations.ts +++ b/src/lib/utils/translations.ts @@ -160,7 +160,7 @@ const getRequiredNamespacesForPath = (path: string) => { primaryNamespace = "page-bug-bounty" } - if (path === "run-a-node") { + if (path.startsWith("/run-a-node")) { primaryNamespace = "page-run-a-node" } @@ -189,6 +189,7 @@ const getRequiredNamespacesForPath = (path: string) => { path.startsWith("/nft") || path.startsWith("/roadmap/merge") || path.startsWith("/roadmap/scaling") || + path.startsWith("/run-a-node") || path.startsWith("/security") || path.startsWith("/staking/solo") || path.startsWith("/wallets") || diff --git a/src/pages/run-a-node.tsx b/src/pages/run-a-node.tsx index 3e1e98de58c..7083f990e27 100644 --- a/src/pages/run-a-node.tsx +++ b/src/pages/run-a-node.tsx @@ -38,6 +38,7 @@ import OldHeading from "@/components/OldHeading" import Text from "@/components/OldText" import PageHero from "@/components/PageHero" import PageMetadata from "@/components/PageMetadata" +import { StandaloneQuizWidget as QuizWidget } from "@/components/Quiz/QuizWidget" import Translation from "@/components/Translation" import { existsNamespace } from "@/lib/utils/existsNamespace" @@ -328,7 +329,7 @@ type RunANodeCard = { } export const getStaticProps = (async ({ locale }) => { - const requiredNamespaces = getRequiredNamespacesForPage("run-a-node") + const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node") const contentNotTranslated = !existsNamespace(locale!, requiredNamespaces[1]) @@ -917,6 +918,7 @@ const RunANodePage = () => { + From 0fc257cad9e8b0065ee526f81e90448a08edf0a5 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:23:01 -0800 Subject: [PATCH 4/6] fix: handling saved results in local storage initialize "completed" to empty object, and add score fallback if not yet taken --- src/components/Quiz/QuizWidget/useQuizWidget.tsx | 4 +++- src/components/Quiz/useLocalQuizData.ts | 11 +---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/components/Quiz/QuizWidget/useQuizWidget.tsx b/src/components/Quiz/QuizWidget/useQuizWidget.tsx index d566865b31e..bd9393f3e6a 100644 --- a/src/components/Quiz/QuizWidget/useQuizWidget.tsx +++ b/src/components/Quiz/QuizWidget/useQuizWidget.tsx @@ -108,7 +108,9 @@ export const useQuizWidget = ({ if (!showResults) return updateUserStats((prevStats) => { - const lastScore = prevStats.completed[quizKey][1] + const { completed } = prevStats + const hasResultsSaved = !!completed[quizKey] + const lastScore = hasResultsSaved ? prevStats.completed[quizKey][1] : 0 if (numberOfCorrectAnswers < lastScore) return prevStats diff --git a/src/components/Quiz/useLocalQuizData.ts b/src/components/Quiz/useLocalQuizData.ts index bf37796102b..7faa3674187 100644 --- a/src/components/Quiz/useLocalQuizData.ts +++ b/src/components/Quiz/useLocalQuizData.ts @@ -6,19 +6,10 @@ import { USER_STATS_KEY } from "@/lib/constants" import { useLocalStorage } from "@/hooks/useLocalStorage" -/** - * Contains each quiz id as key and to indicate if its completed and the highest score in that quiz - * - * Initialize all quizzes as not completed - */ -const INITIAL_COMPLETED_QUIZZES: CompletedQuizzes = Object.keys( - allQuizzesData -).reduce((object, key) => ({ ...object, [key]: [false, 0] }), {}) - export const INITIAL_USER_STATS: UserStats = { score: 0, average: [], - completed: INITIAL_COMPLETED_QUIZZES, + completed: {}, } export const useLocalQuizData = () => { From 8546c0b2f0933307cc3955269b751a38ad2c3f29 Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:51:42 -0800 Subject: [PATCH 5/6] fix: add backwards compatibility for string values --- src/components/Quiz/useLocalQuizData.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/Quiz/useLocalQuizData.ts b/src/components/Quiz/useLocalQuizData.ts index 7faa3674187..02316e246cd 100644 --- a/src/components/Quiz/useLocalQuizData.ts +++ b/src/components/Quiz/useLocalQuizData.ts @@ -1,6 +1,4 @@ -import { CompletedQuizzes, UserStats } from "@/lib/types" - -import allQuizzesData from "@/data/quizzes" +import { UserStats } from "@/lib/types" import { USER_STATS_KEY } from "@/lib/constants" @@ -15,5 +13,13 @@ export const INITIAL_USER_STATS: UserStats = { export const useLocalQuizData = () => { const data = useLocalStorage(USER_STATS_KEY, INITIAL_USER_STATS) + // If the user has an old version of the app, convert the + // `completed` value from a string to an object. + const [current, setCurrent] = data + if (typeof current.completed === "string") { + const newCompleted = JSON.parse(current.completed) + setCurrent({ ...current, completed: newCompleted }) + } + return data } From 0813e436fae5d961d5da02df81646d8e35f060bd Mon Sep 17 00:00:00 2001 From: Paul Wackerow <54227730+wackerow@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:13:42 -0800 Subject: [PATCH 6/6] refactor: rm intermediate variable --- src/components/Quiz/useLocalQuizData.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Quiz/useLocalQuizData.ts b/src/components/Quiz/useLocalQuizData.ts index 02316e246cd..733e030f857 100644 --- a/src/components/Quiz/useLocalQuizData.ts +++ b/src/components/Quiz/useLocalQuizData.ts @@ -17,8 +17,7 @@ export const useLocalQuizData = () => { // `completed` value from a string to an object. const [current, setCurrent] = data if (typeof current.completed === "string") { - const newCompleted = JSON.parse(current.completed) - setCurrent({ ...current, completed: newCompleted }) + setCurrent({ ...current, completed: JSON.parse(current.completed) }) } return data