├── .logs ├── .cwd.log ├── .temp.log ├── .history_cwd.log ├── .next_command.log ├── .terminal-out.log └── .bash_history.log ├── learn-basic-rust-by-building-a-cli-wallet └── README.md ├── learn-digital-ledgers-by-building-a-blockchain └── .gitkeep ├── learn-intermediate-rust-by-building-a-blockchain └── README.md ├── learn-smart-contracts-by-building-a-dumb-contract └── README.md ├── learn-websockets-by-building-a-blockchain-explorer └── README.md ├── .prettierignore ├── learn-proof-of-stake-consensus-by-completing-a-web3-game ├── client │ ├── README.md │ ├── .babelrc │ ├── src │ │ ├── tutorial │ │ │ ├── index.js │ │ │ └── state.js │ │ ├── components │ │ │ ├── ground.js │ │ │ ├── navigation.js │ │ │ ├── ceiling.js │ │ │ ├── screen-nav.js │ │ │ ├── monitor.js │ │ │ ├── light-bulb.js │ │ │ ├── chain.css │ │ │ ├── server.css │ │ │ ├── light-bulb.css │ │ │ ├── chain.js │ │ │ ├── server.js │ │ │ ├── monitor.css │ │ │ ├── camperbot.js │ │ │ ├── screen.js │ │ │ ├── camperbot.css │ │ │ └── main-view.js │ │ ├── setupTests.js │ │ ├── tools │ │ │ ├── glow.js │ │ │ ├── utils.js │ │ │ ├── socket.js │ │ │ ├── handle-tasks.js │ │ │ └── draggable.js │ │ ├── node-state.js │ │ ├── index.js │ │ └── index.css │ ├── public │ │ ├── robots.txt │ │ ├── favicon-32x32.png │ │ ├── chain.json │ │ ├── index.html │ │ ├── prism.css │ │ ├── bubbles.json │ │ └── quiz.json │ └── package.json ├── .gitpod.yml ├── .gitignore ├── node │ ├── package.json │ ├── state.js │ ├── handle-client.js │ ├── events │ │ ├── index.js │ │ ├── utils.js │ │ ├── client-events.js │ │ └── node-events.js │ ├── index.js │ ├── handle-node.js │ └── node_trace.1.log ├── blockchain │ ├── src │ │ ├── block.rs │ │ └── lib.rs │ ├── Cargo.toml │ └── tests │ │ └── handle_mine.rs ├── utils │ ├── logger.js │ ├── perf-metrics.js │ ├── parser │ │ └── index.js │ └── websockets │ │ └── index.js ├── package.json ├── assets │ ├── quiz.json │ └── quiz.md └── README.md ├── bash ├── sourcerer.sh └── .bashrc ├── config ├── state.json └── projects.json ├── learn-proof-of-work-consensus-by-building-a-block-mining-algorithm ├── transactions.json ├── validate-chain.js ├── init-blockchain.js ├── add-transaction.js ├── add-block.js ├── blockchain-helpers.js └── blockchain.json ├── curriculum └── locales │ └── english │ ├── learn-rpc-and-idls-by-building-a-dapp.md │ ├── learn-websockets-by-building-a-chat-app.md │ ├── learn-basic-rust-by-building-a-cli-wallet.md │ ├── learn-nfts-by-building-a-set-of-concert-tickets.md │ ├── learn-smart-contracts-by-building-a-car-loan-contract.md │ ├── learn-proof-of-stake-consensus-by-completing-a-web3-game.md │ ├── learn-smart-contracts-by-building-a-dumb-contract-in-rust.md │ ├── learn-distributed-networks-by-building-a-distributed-chat-app.md │ ├── learn-intermediate-rust-by-building-a-blockchain.md │ ├── build-a-smart-contract-in-rust.md │ └── build-a-peer-to-peer-network.md ├── .gitignore ├── .prettierrc ├── learn-digital-signatures-by-building-a-wallet ├── validate-chain.js ├── get-address-balance.js ├── init-blockchain.js ├── transactions.json ├── add-transaction.js ├── mine-block.js ├── blockchain.json └── blockchain-helpers.js ├── .github └── ISSUE_TEMPLATE │ ├── HELP.md │ └── BUG.md ├── .editorconfig ├── .gitpod.Dockerfile ├── .devcontainer.json ├── tooling ├── rejig.js ├── camper-info.js └── helpers.js ├── .gitpod.yml ├── package.json ├── Dockerfile ├── LICENSE ├── freecodecamp.conf.json ├── .vscode └── settings.json └── README.md /.logs/.cwd.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.logs/.temp.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.logs/.history_cwd.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.logs/.next_command.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.logs/.terminal-out.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.logs/.bash_history.log: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /learn-basic-rust-by-building-a-cli-wallet/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-digital-ledgers-by-building-a-blockchain/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-intermediate-rust-by-building-a-blockchain/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-smart-contracts-by-building-a-dumb-contract/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-websockets-by-building-a-blockchain-explorer/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/.cache 2 | **/package-lock.json 3 | **/pkg 4 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/.babelrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /bash/sourcerer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ./bash/.bashrc 3 | echo "BashRC Sourced" 4 | -------------------------------------------------------------------------------- /config/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "currentProject": null, 3 | "locale": "english" 4 | } 5 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tutorial/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/transactions.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /curriculum/locales/english/learn-rpc-and-idls-by-building-a-dapp.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-websockets-by-building-a-chat-app.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-basic-rust-by-building-a-cli-wallet.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-nfts-by-building-a-set-of-concert-tickets.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-smart-contracts-by-building-a-car-loan-contract.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-proof-of-stake-consensus-by-completing-a-web3-game.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-smart-contracts-by-building-a-dumb-contract-in-rust.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-distributed-networks-by-building-a-distributed-chat-app.md: -------------------------------------------------------------------------------- 1 | # Web3 - Placeholder 2 | 3 | ## 1 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | Cargo.lock 4 | build-a-web3-client-side-package-for-your-dapp/fixture/data/tmp/**/* 5 | !.gitkeep 6 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/ground.js: -------------------------------------------------------------------------------- 1 | const Ground = () => { 2 | return
; 3 | }; 4 | 5 | export default Ground; 6 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/navigation.js: -------------------------------------------------------------------------------- 1 | const Navigation = () => { 2 | return ; 3 | }; 4 | 5 | export default Navigation; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": true, 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "none", 8 | "arrowParens": "avoid" 9 | } 10 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/web3-curriculum/HEAD/learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/validate-chain.js: -------------------------------------------------------------------------------- 1 | import { isValidChain } from './blockchain-helpers.js'; 2 | 3 | if (isValidChain()) { 4 | console.log('Chain is valid'); 5 | } else { 6 | console.log('Chain is not valid'); 7 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/HELP.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help Needed 3 | about: Get help with a project or lesson 4 | title: '[HELP]: ' 5 | --- 6 | 7 | ### Project 8 | 9 | ### Lesson Number 10 | 11 | ### Question 12 | 13 | ### Code and Screenshots 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Found 3 | about: Report a bug with the platform/content 4 | title: '[BUG]: ' 5 | --- 6 | 7 | ### Issue/Experience 8 | 9 | ### Output of running `node tooling/camper-info.js` from the workspace root 10 | 11 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/validate-chain.js: -------------------------------------------------------------------------------- 1 | import { isValidChain } from './blockchain-helpers.js'; 2 | 3 | if (isValidChain()) { 4 | console.log('Chain is valid'); 5 | } else { 6 | console.log('Chain is not valid'); 7 | } -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/get-address-balance.js: -------------------------------------------------------------------------------- 1 | import { getAddressBalance } from './blockchain-helpers.js'; 2 | 3 | const nameOfAddress = process.argv[2]; 4 | const balance = getAddressBalance(nameOfAddress); 5 | 6 | console.log(`The balance for ${nameOfAddress} is ${balance}`); -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/init-blockchain.js: -------------------------------------------------------------------------------- 1 | import { writeBlockchain } from './blockchain-helpers.js'; 2 | 3 | const genesisBlock = { 4 | hash: "0", 5 | previousHash: null 6 | } 7 | 8 | const blockchain = [genesisBlock]; 9 | writeBlockchain(blockchain); -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: npm install && npm run build:blockchain && npm run build:client 3 | 4 | ports: 5 | - port: 3000 6 | visibility: private 7 | onOpen: open-preview 8 | 9 | vscode: 10 | extensions: 11 | - dbaeumer.vscode-eslint 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [package.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/init-blockchain.js: -------------------------------------------------------------------------------- 1 | import { writeBlockchain, writeTransactions } from './blockchain-helpers.js'; 2 | 3 | const genesisBlock = { 4 | hash: "0", 5 | previousHash: null 6 | } 7 | 8 | const blockchain = [genesisBlock]; 9 | writeBlockchain(blockchain); 10 | writeTransactions([]); -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/transactions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fromAddress": null, 4 | "toAddress": "Me", 5 | "amount": 50 6 | }, 7 | { 8 | "hash": "187116b5a916e63287cdb275a2e6cab646fde335639f820a0e4531ad444ea7b8", 9 | "fromAddress": "Me", 10 | "toAddress": "I", 11 | "amount": 5 12 | } 13 | ] -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/chain.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "metadata": {}, 4 | "transactions": [], 5 | "nonce": "", 6 | "prev_hash": null, 7 | "hash": "" 8 | }, 9 | { 10 | "metadata": {}, 11 | "transactions": [], 12 | "nonce": "", 13 | "prev_hash": "", 14 | "hash": "" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/ceiling.js: -------------------------------------------------------------------------------- 1 | import LightBulb from "./light-bulb"; 2 | 3 | const Ceiling = ({ isLightOn, toggleLight }) => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default Ceiling; 12 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full:latest 2 | 3 | RUN bash -c 'VERSION="18" \ 4 | && source $HOME/.nvm/nvm.sh && nvm install $VERSION \ 5 | && nvm use $VERSION && nvm alias default $VERSION' 6 | 7 | RUN echo "nvm use default &>/dev/null" >> ~/.bashrc.d/51-nvm-fix 8 | 9 | RUN sudo apt-get update && sudo apt-get upgrade -y 10 | RUN sudo apt-get install -y firefox 11 | RUN cargo install wasm-pack 12 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/screen-nav.js: -------------------------------------------------------------------------------- 1 | const ScreenNav = () => { 2 | return ( 3 |
4 | 9 |
10 | ); 11 | }; 12 | 13 | export default ScreenNav; 14 | -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "customizations": { 3 | "vscode": { 4 | "extensions": [ 5 | "dbaeumer.vscode-eslint", 6 | "freeCodeCamp.freecodecamp-courses@1.7.4", 7 | "freeCodeCamp.freecodecamp-dark-vscode-theme" 8 | ] 9 | } 10 | }, 11 | "forwardPorts": [8080], 12 | "workspaceFolder": "/workspace/web3-curriculum", 13 | "dockerFile": "./Dockerfile", 14 | "context": "." 15 | } 16 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tools/glow.js: -------------------------------------------------------------------------------- 1 | export default function glow(element, isLightOn) { 2 | if (isLightOn) return {}; 3 | const el = document.querySelector(element); 4 | const blurSize = el?.clientWidth / 10; 5 | const spreadSize = el?.clientWidth / 10; 6 | const colour = el?.style?.backgroundColor; 7 | return { boxShadow: `0px 0px ${blurSize}px ${spreadSize}px ${colour}` }; 8 | } 9 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tools/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes an array, and randomly scrambles the order of its elements. 3 | * @param {Object[]} options - An array of objects with the following properties. 4 | * @returns {Object[]} - The scrambled array 5 | */ 6 | export function scramble(options) { 7 | const shuffled = options?.sort(() => Math.random() - 0.5) ?? []; 8 | return shuffled; 9 | } 10 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/monitor.js: -------------------------------------------------------------------------------- 1 | import "./monitor.css"; 2 | 3 | import Screen from "./screen"; 4 | 5 | const Monitor = ({ task, isLightOn }) => { 6 | return ( 7 |
8 | 9 |
10 |
11 |
12 | ); 13 | }; 14 | 15 | export default Monitor; 16 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/add-transaction.js: -------------------------------------------------------------------------------- 1 | import { writeTransactions, getTransactions } from './blockchain-helpers.js'; 2 | 3 | const fromAddress = process.argv[2]; 4 | const toAddress = process.argv[3]; 5 | const amount = parseInt(process.argv[4]); 6 | 7 | const newTransaction = { 8 | fromAddress, 9 | toAddress, 10 | amount 11 | } 12 | 13 | const transactions = getTransactions(); 14 | transactions.push(newTransaction); 15 | writeTransactions(transactions); -------------------------------------------------------------------------------- /tooling/rejig.js: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from 'fs/promises'; 2 | 3 | const PATH = 4 | '../curriculum/locales/english/learn-digital-signatures-by-building-a-wallet.md'; 5 | 6 | /** 7 | * Ensures all lessons are incremented by 1 8 | */ 9 | async function rejig() { 10 | const file = await readFile(PATH, 'utf-8'); 11 | let lessonNumber = 0; 12 | const newFile = file.replace(/## \d+/g, () => { 13 | lessonNumber++; 14 | return `## ${lessonNumber}`; 15 | }); 16 | await writeFile(PATH, newFile, 'utf-8'); 17 | } 18 | 19 | rejig(); 20 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | target 4 | bundle.js 5 | 6 | 7 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 8 | 9 | # dependencies 10 | /.pnp 11 | .pnp.js 12 | 13 | # testing 14 | /coverage 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | dist 31 | .parcel-cache 32 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | # Commands to start on workspace startup 5 | tasks: 6 | - init: npm ci 7 | 8 | ports: 9 | - port: 8080 10 | onOpen: open-preview 11 | 12 | # TODO: See about publishing to Open VSX for smoother process 13 | vscode: 14 | extensions: 15 | - https://github.com/freeCodeCamp/courses-vscode-extension/releases/download/v1.7.4/freecodecamp-courses-1.7.4.vsix 16 | - https://github.com/freeCodeCamp/freecodecamp-dark-vscode-theme/releases/download/v1.0.0/freecodecamp-dark-vscode-theme-1.0.0.vsix 17 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/add-block.js: -------------------------------------------------------------------------------- 1 | import { getBlockchain, writeBlockchain, getTransactions, writeTransactions } from './blockchain-helpers.js'; 2 | 3 | const blockchain = getBlockchain(); 4 | const previousBlock = blockchain[blockchain.length - 1]; 5 | const transactions = getTransactions(); 6 | 7 | const newBlock = { 8 | hash: Math.random().toString(), 9 | previousHash: previousBlock.hash, 10 | transactions 11 | } 12 | 13 | blockchain.push(newBlock); 14 | writeBlockchain(blockchain); 15 | writeTransactions([]); -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/ShaunSHamilton/silly-websocket.git" 8 | }, 9 | "main": "index.js", 10 | "scripts": { 11 | "dev": "node --stack-trace-limit=2 index.js", 12 | "start": "node index.js" 13 | }, 14 | "type": "module", 15 | "author": "Shaun Hamilton", 16 | "license": "ISC", 17 | "dependencies": { 18 | "ws": "^8.5.0" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^2.0.15" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/light-bulb.js: -------------------------------------------------------------------------------- 1 | import "./light-bulb.css"; 2 | 3 | const LightBulb = ({ isLightOn, toggleLight }) => { 4 | return ( 5 |
{ 8 | e.stopPropagation(); 9 | toggleLight(); 10 | }} 11 | > 12 |
13 |
14 | {isLightOn && ( 15 | <> 16 |
17 |
18 | 19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export default LightBulb; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-curriculum", 3 | "version": "1.0.0", 4 | "description": "Root package.json", 5 | "scripts": { 6 | "test": "cd build-a-web3-client-side-package-for-your-dapp && node fixture/provider.js && cd .." 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/freeCodeCamp/web3-curriculum.git" 11 | }, 12 | "author": "Shaun Hamilton", 13 | "type": "module", 14 | "dependencies": { 15 | "@freecodecamp/freecodecamp-os": "1.8.3", 16 | "babeliser": "0.6.0", 17 | "crypto-js": "4.1.1", 18 | "dotenv": "16.0.2", 19 | "elliptic": "6.5.4", 20 | "express": "4.18.1", 21 | "logover": "^2.0.0", 22 | "ws": "8.8.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | freeCodeCamp: Intern Node 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/state.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Name given to Node when it is created. Must be unique 3 | */ 4 | export const NAME = process.env.NAME; 5 | 6 | export const nodeState = { 7 | chain: [], 8 | clientSocks: [], 9 | isNextMiner: function () { 10 | return this.chain[this.chain.length - 1].next_miner === this.name; 11 | }, 12 | isNextValidator: function () { 13 | return this.chain[this.chain.length - 1].next_validators.includes( 14 | this.name 15 | ); 16 | }, 17 | name: NAME, 18 | network: new Set(), 19 | nodeSocks: [], 20 | tasks: [], 21 | transactionPool: [], // List of transaction since last block was mined. Gets added to next block. 22 | }; 23 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/chain.css: -------------------------------------------------------------------------------- 1 | #chain-container { 2 | position: absolute; 3 | top: 5px; 4 | left: 5px; 5 | width: 35vw; 6 | height: 50vh; 7 | background-color: black; 8 | opacity: 0.75; 9 | font-size: 0.8rem; 10 | border-radius: 5px; 11 | overflow: auto; 12 | } 13 | 14 | #chain-container > h2 { 15 | text-align: center; 16 | } 17 | 18 | #transaction-pool-container { 19 | position: absolute; 20 | top: 50vh; 21 | left: 5px; 22 | width: 30vw; 23 | height: 50vh; 24 | background-color: black; 25 | opacity: 0.75; 26 | font-size: 0.8rem; 27 | border-radius: 5px; 28 | overflow: auto; 29 | } 30 | 31 | #transaction-pool-container > h2 { 32 | text-align: center; 33 | } 34 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/blockchain/src/block.rs: -------------------------------------------------------------------------------- 1 | //! # Block 2 | //! 3 | //! A block is a piece of data that is stored in the blockchain. 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::node::Node; 8 | 9 | /// The block added to the chain of the blockchain. 10 | /// 11 | /// **Note:** This is a reference type, and does not contain any implementations. 12 | #[derive(Serialize, Deserialize, Debug, Clone)] 13 | pub struct Block { 14 | pub id: u64, 15 | pub hash: String, 16 | pub previous_hash: String, 17 | pub timestamp: u64, // TODO: Consider removing if testing too difficult 18 | pub data: Vec, 19 | pub nonce: u64, 20 | pub next_miner: String, 21 | pub next_validators: Vec, 22 | } 23 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/blockchain/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blockchain" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] } 13 | wasm-bindgen-test = "0.3.29" 14 | chrono = { version = "0.4.19", features = ["wasmbind"] } 15 | sha2 = "0.10.2" 16 | serde = { version = "1.0.136", features = ["derive"] } 17 | serde_json = "1.0.79" 18 | hex = "0.4.3" 19 | rand = "0.8.5" 20 | getrandom = { version = "0.2.6", features = ["js"] } 21 | web-sys = { version = "0.3.56", features = ["console", "ErrorEvent"] } 22 | 23 | [profile.release] 24 | debug = true 25 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/add-transaction.js: -------------------------------------------------------------------------------- 1 | import sha256 from 'crypto-js/sha256.js'; 2 | import { writeTransactions, getTransactions, getAddressBalance } from './blockchain-helpers.js'; 3 | 4 | const fromAddress = process.argv[2]; 5 | const toAddress = process.argv[3]; 6 | const amount = parseInt(process.argv[4]); 7 | 8 | const hash = sha256(fromAddress + toAddress + amount).toString(); 9 | 10 | const newTransaction = { 11 | hash, 12 | fromAddress, 13 | toAddress, 14 | amount 15 | } 16 | 17 | const transactions = getTransactions(); 18 | const addressBalance = getAddressBalance(fromAddress); 19 | 20 | if (addressBalance >= amount) { 21 | transactions.push(newTransaction); 22 | writeTransactions(transactions); 23 | } else { 24 | console.log('You do not have enough funds to make that transaction') 25 | } -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/utils/logger.js: -------------------------------------------------------------------------------- 1 | const LEVEL = process.env.LOG_LEVEL || "info"; 2 | 3 | const LogLevel = { 4 | debug: 0, 5 | info: 1, 6 | warn: 2, 7 | error: 3, 8 | }; 9 | 10 | export function info(...args) { 11 | if (LogLevel[LEVEL] <= LogLevel.info) { 12 | console.info("🔵%cINFO: ", "color: blue", ...args); 13 | } 14 | } 15 | export function warn(...args) { 16 | if (LogLevel[LEVEL] <= LogLevel.warn) { 17 | console.warn("🟠%cWARN: ", "color: orange", ...args); 18 | } 19 | } 20 | export function error(...args) { 21 | if (LogLevel[LEVEL] <= LogLevel.error) { 22 | console.error("🔴%cERROR: ", "color: red", ...args); 23 | } 24 | } 25 | export function debug(...args) { 26 | if (LogLevel[LEVEL] === LogLevel.debug) { 27 | // console.trace("[DEBUG]"); 28 | console.debug("🟢%cDEBUG: ", "color: green", ...args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/mine-block.js: -------------------------------------------------------------------------------- 1 | import sha256 from 'crypto-js/sha256.js'; 2 | import { getBlockchain, writeBlockchain, getTransactions, writeTransactions } from './blockchain-helpers.js'; 3 | 4 | const blockchain = getBlockchain(); 5 | const previousBlock = blockchain[blockchain.length - 1]; 6 | const transactions = getTransactions(); 7 | 8 | let nonce = 0; 9 | let hash = sha256(nonce + previousBlock.hash + JSON.stringify(transactions)).toString(); 10 | const difficulty = 2; 11 | 12 | while (!hash.startsWith('0'.repeat(difficulty))) { 13 | nonce++; 14 | hash = sha256(nonce + previousBlock.hash + JSON.stringify(transactions)).toString(); 15 | } 16 | 17 | const newBlock = { 18 | hash, 19 | nonce, 20 | previousHash: previousBlock.hash, 21 | transactions 22 | } 23 | 24 | const rewardTransaction = { 25 | fromAddress: null, 26 | toAddress: 'Me', 27 | amount: 50 28 | } 29 | 30 | blockchain.push(newBlock); 31 | writeBlockchain(blockchain); 32 | writeTransactions([rewardTransaction]); -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tools/socket.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const SocketContext = createContext(null); 4 | 5 | export const tasks = (data) => 6 | new CustomEvent("tasks", { detail: { tasks: data.tasks } }); 7 | 8 | export const chain = (data) => 9 | new CustomEvent("chain", { detail: { chain: data.chain } }); 10 | 11 | export const transactionPool = (data) => 12 | new CustomEvent("transactionPool", { 13 | detail: { transactionPool: data.transactionPool }, 14 | }); 15 | 16 | const name = (data) => new CustomEvent("name", { detail: { name: data.name } }); 17 | 18 | export const handleEvents = (socket, { type, data }) => { 19 | const eventGroups = { 20 | connect: [name, chain, transactionPool, tasks], 21 | ping: [], 22 | "update-chain": [chain, transactionPool, tasks], 23 | }; 24 | const eventGroup = eventGroups[type] ?? []; 25 | for (const event of eventGroup) { 26 | socket.dispatchEvent(event(data)); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/node-state.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import { debug, info } from "../../utils/logger"; 3 | 4 | export const NameContext = createContext(null); 5 | 6 | export function getSelf(name, chain) { 7 | for (const block of chain) { 8 | for (const node of block.data) { 9 | if (node.name === name) { 10 | return node; 11 | } 12 | } 13 | } 14 | return { tokens: 0, staked: 0, reputation: 0, racks: 0 }; 15 | } 16 | 17 | export const dispatchStake = (socket) => { 18 | info("Dispatching stake"); 19 | socket.sock({}, "stake"); 20 | }; 21 | export const dispatchUnstake = (socket) => { 22 | debug("Dispatching unstake"); 23 | socket.sock({}, "unstake"); 24 | }; 25 | export const dispatchSubmitTask = (socket, task) => { 26 | // info("Dispatching task", NodeContext); 27 | socket.sock(task, "submit-task"); 28 | }; 29 | export const dispatchBuyRack = (socket) => { 30 | debug("Dispatching buy rack"); 31 | socket.sock({}, "buy-rack"); 32 | }; 33 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/server.css: -------------------------------------------------------------------------------- 1 | .server-stack { 2 | display: flex; 3 | flex-wrap: wrap; 4 | flex-direction: column-reverse; 5 | max-width: 60vw; 6 | max-height: 30vh; 7 | height: fit-content; 8 | width: fit-content; 9 | align-items: center; 10 | } 11 | /* If screen width is less than 900px */ 12 | @media screen and (max-width: 900px) { 13 | .server-stack { 14 | max-width: 80vw; 15 | } 16 | } 17 | .server { 18 | width: var(--server-width); 19 | aspect-ratio: 5 / 1; 20 | background-color: black; 21 | border: #444 solid var(--server-border-width); 22 | display: flex; 23 | flex-direction: row-reverse; 24 | flex-wrap: wrap-reverse; 25 | gap: 10px; 26 | } 27 | 28 | .status-led { 29 | width: 10px; 30 | height: 5px; 31 | flex-basis: 10px; 32 | margin: auto 0; 33 | } 34 | .status-led:hover { 35 | cursor: pointer; 36 | } 37 | 38 | @keyframes blink { 39 | 0% { 40 | opacity: 0; 41 | } 42 | 50% { 43 | opacity: 1; 44 | } 45 | 100% { 46 | opacity: 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/light-bulb.css: -------------------------------------------------------------------------------- 1 | .light-bulb { 2 | margin-top: 10px; 3 | display: flex; 4 | align-items: center; 5 | flex-direction: column; 6 | height: fit-content; 7 | } 8 | .wire { 9 | border-top-left-radius: 4px; 10 | border-top-right-radius: 4px; 11 | width: 7px; 12 | height: 80px; 13 | background-color: black; 14 | } 15 | .shade { 16 | width: 60px; 17 | height: 50px; 18 | border-top-left-radius: 100px; 19 | border-top-right-radius: 100px; 20 | background-color: black; 21 | z-index: 1; 22 | } 23 | .bulb { 24 | background-color: yellow; 25 | width: 25px; 26 | height: 25px; 27 | border-radius: 50%; 28 | margin: -20px auto; 29 | z-index: 0; 30 | } 31 | .light { 32 | top: 620px; 33 | width: 2120px; 34 | aspect-ratio: 1 / 1; 35 | position: fixed; 36 | background: rgb(238, 255, 0); 37 | background: linear-gradient( 38 | to right bottom, 39 | rgba(251, 255, 0, 0.301), 40 | 6%, 41 | rgba(78, 83, 34, 0), 42 | 30%, 43 | transparent 44 | ); 45 | transform: rotate(45deg); 46 | } 47 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/blockchain.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hash": "0", 4 | "previousHash": null 5 | }, 6 | { 7 | "hash": "009b0ffdf8024c9fad6291d8b00915d23965a6ed55ba8ed680549076152705fb", 8 | "nonce": 39, 9 | "previousHash": "0", 10 | "transactions": [] 11 | }, 12 | { 13 | "hash": "00c23c20fec7b56b1f9701c07387d8f501e1901b467ca414e2a4d23712dd845f", 14 | "nonce": 930, 15 | "previousHash": "009b0ffdf8024c9fad6291d8b00915d23965a6ed55ba8ed680549076152705fb", 16 | "transactions": [ 17 | { 18 | "fromAddress": null, 19 | "toAddress": "Me", 20 | "amount": 50 21 | } 22 | ] 23 | }, 24 | { 25 | "hash": "006d0fd2f22fbb1701fc4c90f2ee5ea032092da5cb26bff259b9fce552ac8cf9", 26 | "nonce": 40, 27 | "previousHash": "00c23c20fec7b56b1f9701c07387d8f501e1901b467ca414e2a4d23712dd845f", 28 | "transactions": [ 29 | { 30 | "fromAddress": null, 31 | "toAddress": "Me", 32 | "amount": 50 33 | }, 34 | { 35 | "hash": "35e19e0b0532e0073d20125e2818c5751507c63feb5954726e4c101e27752a12", 36 | "fromAddress": "Me", 37 | "toAddress": "You", 38 | "amount": 40 39 | } 40 | ] 41 | } 42 | ] -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "marked": "^4.0.12", 10 | "peer-id": "^0.16.0", 11 | "prismjs": "^1.27.0", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "react-scripts": "5.0.0", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "parcel ./public/index.html --port 8080", 19 | "watch": "parcel watch ./public/index.html --port 8080", 20 | "build": "parcel build ./public/index.html", 21 | "dev": "serve -s dist -l 8080" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "parcel": "^2.3.2", 43 | "serve": "^13.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/blockchain-helpers.js: -------------------------------------------------------------------------------- 1 | import { writeFileSync, readFileSync } from 'fs'; 2 | 3 | export function writeBlockchain(blockchain) { 4 | const blockchainString = JSON.stringify(blockchain, null, 2); 5 | writeFileSync('./blockchain.json', blockchainString); 6 | } 7 | 8 | export function getBlockchain() { 9 | const blockchainFile = readFileSync('./blockchain.json'); 10 | const blockchain = JSON.parse(blockchainFile); 11 | return blockchain; 12 | } 13 | 14 | export function isValidChain() { 15 | const blockchain = getBlockchain(); 16 | 17 | for (let i = 1; i < blockchain.length; i++) { 18 | const previousBlock = blockchain[i - 1]; 19 | const { previousHash } = blockchain[i]; 20 | 21 | if (previousHash !== previousBlock.hash) { 22 | return false; 23 | } 24 | } 25 | 26 | return true; 27 | } 28 | 29 | export function writeTransactions(transactions) { 30 | const transactionsString = JSON.stringify(transactions, null, 2); 31 | writeFileSync('./transactions.json', transactionsString); 32 | } 33 | 34 | export function getTransactions() { 35 | const transactionsFile = readFileSync('./transactions.json'); 36 | const transactions = JSON.parse(transactionsFile); 37 | return transactions; 38 | } -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proof-of-stake", 3 | "version": "1.0.0", 4 | "description": "freeCodeCamp Web3 curriculum project", 5 | "main": "node/index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "build:blockchain": "wasm-pack build --debug blockchain --target nodejs", 11 | "build:client": "npm -w=client run build", 12 | "build:quiz": "node ./utils/parser/index.js", 13 | "dev:client": "npm -w=client run watch", 14 | "dev:node": "LOG_LEVEL=debug npm -w=node run dev", 15 | "start:client": "npm -w=client run start", 16 | "start:node": "npm -w=node run start" 17 | }, 18 | "workspaces": [ 19 | "client", 20 | "node" 21 | ], 22 | "type": "module", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/ShaunSHamilton/proof-of-stake.git" 26 | }, 27 | "keywords": [ 28 | "freeCodeCamp", 29 | "web3", 30 | "course", 31 | "curriculum", 32 | "proof", 33 | "of", 34 | "stake" 35 | ], 36 | "author": "Shaun Hamilton", 37 | "license": "CC-BY-SA-4.0", 38 | "bugs": { 39 | "url": "https://github.com/ShaunSHamilton/proof-of-stake/issues" 40 | }, 41 | "homepage": "https://github.com/ShaunSHamilton/proof-of-stake#readme", 42 | "devDependencies": { 43 | "js-yaml": "^4.1.0", 44 | "wasm-pack": "^0.10.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ARG USERNAME=camper 4 | ARG REPO_NAME=web3-curriculum 5 | ARG HOMEDIR=/workspace/$REPO_NAME 6 | 7 | ENV TZ="America/New_York" 8 | 9 | RUN apt-get update && apt-get install -y sudo 10 | 11 | # Unminimize Ubuntu to restore man pages 12 | RUN yes | unminimize 13 | 14 | # Set up timezone 15 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 16 | 17 | # Set up user, disable pw, and add to sudo group 18 | RUN adduser --disabled-password \ 19 | --gecos '' ${USERNAME} 20 | 21 | RUN adduser ${USERNAME} sudo 22 | 23 | RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> \ 24 | /etc/sudoers 25 | 26 | # Install packages for projects 27 | RUN sudo apt-get install -y curl git bash-completion man-db firefox 28 | 29 | # Install Node LTS 30 | RUN curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - 31 | RUN sudo apt-get install -y nodejs 32 | 33 | # Rust 34 | RUN sudo apt-get install -y build-essential 35 | RUN curl https://sh.rustup.rs -sSf | sh -s -- -y 36 | ENV PATH="/root/.cargo/bin:${PATH}" 37 | 38 | # wasm-pack 39 | RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 40 | 41 | # /usr/lib/node_modules is owned by root, so this creates a folder ${USERNAME} 42 | # can use for npm install --global 43 | WORKDIR ${HOMEDIR} 44 | RUN mkdir ~/.npm-global 45 | RUN npm config set prefix '~/.npm-global' 46 | 47 | # Configure course-specific environment 48 | COPY . . 49 | WORKDIR ${HOMEDIR} 50 | 51 | RUN cd ${HOMEDIR} && npm install 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Shaun Hamilton 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import Navigation from "./components/navigation"; 5 | import MainView from "./components/main-view"; 6 | import { clientWebSocket } from "./tools/handle-tasks"; 7 | import { NameContext } from "./node-state"; 8 | import { chain, SocketContext } from "./tools/socket"; 9 | import { sampleChain } from "./tutorial/state"; 10 | 11 | const App = () => { 12 | const [socket, setSocket] = React.useState(null); 13 | const [name, setName] = React.useState("Camper"); 14 | const [isTutorialing, setIsTutorialing] = React.useState(true); 15 | 16 | React.useEffect(() => { 17 | (async () => { 18 | const socket = await clientWebSocket(isTutorialing); 19 | setSocket(socket); 20 | if (!isTutorialing) { 21 | socket.addEventListener("name", ({ detail: { name } }) => { 22 | setName(name); 23 | }); 24 | } else { 25 | socket.dispatchEvent(chain({ chain: sampleChain })); 26 | } 27 | })(); 28 | // eslint-disable-next-line react-hooks/exhaustive-deps 29 | }, [isTutorialing]); 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | ReactDOM.render( 42 | 43 | 44 | , 45 | document.getElementById("root") 46 | ); 47 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/chain.js: -------------------------------------------------------------------------------- 1 | import { marked } from "marked"; 2 | import Prism from "prismjs"; 3 | import { useContext, useEffect, useState } from "react"; 4 | import { SocketContext } from "../tools/socket"; 5 | import "./chain.css"; 6 | 7 | const Chain = ({ transactionPool }) => { 8 | const socket = useContext(SocketContext); 9 | const [chain, setChain] = useState([]); 10 | 11 | useEffect(() => { 12 | if (socket) { 13 | socket.addEventListener("chain", ({ detail: { chain } }) => { 14 | setChain(chain); 15 | }); 16 | } 17 | }, [socket]); 18 | 19 | return ( 20 | <> 21 |
29 |
41 | 42 | ); 43 | }; 44 | 45 | marked.setOptions({ 46 | highlight: (code, lang) => { 47 | if (Prism.languages[lang]) { 48 | return Prism.highlight(code, Prism.languages[lang], lang); 49 | } else { 50 | return code; 51 | } 52 | }, 53 | }); 54 | 55 | function parseMarkdown(markdown = "") { 56 | return marked.parse(markdown, { gfm: true }); 57 | } 58 | 59 | export default Chain; 60 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/handle-client.js: -------------------------------------------------------------------------------- 1 | import { WebSocketServer } from "ws"; 2 | import { 3 | parseBuffer, 4 | parse, 5 | findAvailablePort, 6 | } from "../utils/websockets/index.js"; 7 | import { info, debug, warn } from "../utils/logger.js"; 8 | import { handleClientEvent } from "./events/index.js"; 9 | import { nodeState } from "./state.js"; 10 | 11 | export async function handleClientWebSocket() { 12 | // Create a server to listen for client connections 13 | const availableClientPort = await findAvailablePort(31000, 31100); 14 | const clientWebSocketServer = new WebSocketServer({ 15 | port: availableClientPort, 16 | }); 17 | info(`Listening for clients on port: ${availableClientPort}`); 18 | clientWebSocketServer.on("connection", (ws, req) => { 19 | ws.on("message", async (requestData) => { 20 | const { type, data } = parseBuffer(requestData); 21 | debug(`[${type}] From client: `, data); 22 | const res = await handleClientEvent({ type, name: nodeState.name, data }); 23 | sock(res, nodeState.name, type); 24 | }); 25 | ws.on("close", (event) => { 26 | info(`Client disconnected: ${event}`); 27 | }); 28 | ws.on("error", (err) => { 29 | warn(`Client connection error: ${err}`); 30 | }); 31 | 32 | sock( 33 | { 34 | chain: nodeState.chain, 35 | tasks: nodeState.tasks, 36 | transactionPool: nodeState.transactionPool, 37 | }, 38 | nodeState.name, 39 | "connect" 40 | ); 41 | nodeState.clientSocks.push(ws); 42 | 43 | function sock(data, name, type) { 44 | ws.send(parse({ type, name, data })); 45 | } 46 | }); 47 | 48 | return availableClientPort; 49 | } 50 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.27.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+c+csharp+cpp+clojure+cobol+csv+dart+docker+elixir+git+go+graphql+haskell+http+java+json+json5+jsonp+julia+kotlin+latex+markup-templating+matlab+php+pug+python+jsx+tsx+regex+ruby+rust+sass+scss+solidity+sql+toml+typescript+yaml */ 3 | code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} 4 | -------------------------------------------------------------------------------- /freecodecamp.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": ".", 3 | "version": "0.1.7", 4 | "scripts": { 5 | "develop-course": "NODE_ENV=development node ./node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/server.js", 6 | "run-course": "NODE_ENV=production node ./node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/server.js", 7 | "test": { 8 | "functionName": "handleMessage", 9 | "arguments": [ 10 | { 11 | "message": "Hello World!", 12 | "type": "info" 13 | } 14 | ] 15 | } 16 | }, 17 | "workspace": { 18 | "previews": [ 19 | { 20 | "open": true, 21 | "url": "http://localhost:8080", 22 | "showLoader": true, 23 | "timeout": 4000 24 | } 25 | ] 26 | }, 27 | "bash": { 28 | ".bashrc": "./bash/.bashrc", 29 | "sourcerer.sh": "./bash/sourcerer.sh" 30 | }, 31 | "client": { 32 | "assets": { 33 | "header": "./client/assets/fcc_primary_large.svg", 34 | "favicon": "./client/assets/fcc_primary_small.svg" 35 | }, 36 | "landing": { 37 | "description": "For these courses, you will use your local development environment to complete interactive tutorials and build projects.\n\nThese courses start off with basic cryptographic concepts. Using Nodejs, you will learn everything from cryptographic hash functions to building your own blockchain.\n\nNext, you will learn about different consensus mechanisms.\n\nFinally, you will learn Rust, and WASM in the context of a blockchain.", 38 | "faq-link": "#", 39 | "faq-text": "https://github.com/freeCodeCamp/web3-curriculum/issues" 40 | } 41 | }, 42 | "config": { 43 | "projects.json": "./config/projects.json", 44 | "state.json": "./config/state.json" 45 | }, 46 | "curriculum": { 47 | "locales": { 48 | "english": "./curriculum/locales/english" 49 | } 50 | }, 51 | "tooling": { 52 | "helpers": "./tooling/helpers.js" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tools/handle-tasks.js: -------------------------------------------------------------------------------- 1 | import { parse, parseBuffer } from "../../../utils/websockets/index"; 2 | import { info, error, warn, debug } from "../../../utils/logger"; 3 | import { handleEvents } from "./socket"; 4 | import { handleTutorial } from "../tutorial/state"; 5 | 6 | export async function clientWebSocket(isTutorialing) { 7 | // Fetch port from server 8 | const { portForClient } = await (await fetch("/port")).json(); 9 | info(`Connected on port ${portForClient}`); 10 | if (!portForClient) { 11 | throw new Error("No port found"); 12 | } 13 | const prom = new Promise((resolve, _reject) => { 14 | const socket = new WebSocket(`ws://localhost:${portForClient}`); 15 | 16 | // Connection opened 17 | socket.addEventListener("open", (_event) => { 18 | info("Connection opened with serverside"); 19 | }); 20 | 21 | // Listen for messages 22 | socket.addEventListener("message", (event) => { 23 | const message = parseBuffer(event.data); 24 | const { data, name, type } = message; 25 | debug(`[${type}] From Server (${name}): `, data); 26 | 27 | if (!isTutorialing) { 28 | handleEvents(socket, { type, data }); 29 | } else { 30 | handleTutorial(socket, { type, data }); 31 | } 32 | }); 33 | socket.addEventListener("error", (err) => { 34 | error(err); 35 | }); 36 | socket.addEventListener("close", (event) => { 37 | warn(`Closed connection with: ${event.code}`); 38 | socket.close(); 39 | }); 40 | 41 | function sock(data, type) { 42 | const parsed = parse({ type, data }); 43 | debug(parsed); 44 | if (!isTutorialing) { 45 | this.send(parsed); 46 | } else { 47 | handleTutorial(socket, { type }); 48 | } 49 | } 50 | 51 | // Attach parsing send function to socket 52 | socket.sock = sock; 53 | 54 | resolve(socket); 55 | }); 56 | return prom; 57 | } 58 | -------------------------------------------------------------------------------- /learn-proof-of-work-consensus-by-building-a-block-mining-algorithm/blockchain.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hash": "0", 4 | "previousHash": null 5 | }, 6 | { 7 | "hash": "0.45062045106488546", 8 | "previousHash": "0" 9 | }, 10 | { 11 | "hash": "0.16111842235772156", 12 | "previousHash": "0.45062045106488546" 13 | }, 14 | { 15 | "hash": "0.8158006630142653", 16 | "previousHash": "0.16111842235772156" 17 | }, 18 | { 19 | "hash": "0.8955003586769703", 20 | "previousHash": "0.8158006630142653", 21 | "data": { 22 | "fromAddress": "Me", 23 | "toAddress": "You", 24 | "amount": 10 25 | } 26 | }, 27 | { 28 | "hash": "0.9583680743350331", 29 | "previousHash": "0.8955003586769703", 30 | "data": { 31 | "fromAddress": "Me", 32 | "toAddress": "You", 33 | "amount": 20 34 | } 35 | }, 36 | { 37 | "hash": "0.939029301911428", 38 | "previousHash": "0.9583680743350331", 39 | "data": { 40 | "fromAddress": "Me", 41 | "toAddress": "You", 42 | "amount": 30 43 | } 44 | }, 45 | { 46 | "hash": "0.7495656727576048", 47 | "previousHash": "0.939029301911428", 48 | "transactions": [ 49 | { 50 | "fromAddress": "You", 51 | "toAddress": "Me", 52 | "amount": 15 53 | }, 54 | { 55 | "fromAddress": "You", 56 | "toAddress": "Me", 57 | "amount": 25 58 | }, 59 | { 60 | "fromAddress": "You", 61 | "toAddress": "Me", 62 | "amount": 35 63 | } 64 | ] 65 | }, 66 | { 67 | "hash": "0.13366310229995104", 68 | "previousHash": "0.7495656727576048", 69 | "transactions": [ 70 | { 71 | "fromAddress": "Me", 72 | "toAddress": "You", 73 | "amount": 2 74 | }, 75 | { 76 | "fromAddress": "Me", 77 | "toAddress": "You", 78 | "amount": 4 79 | }, 80 | { 81 | "fromAddress": "Me", 82 | "toAddress": "You", 83 | "amount": 6 84 | } 85 | ] 86 | } 87 | ] -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/utils/perf-metrics.js: -------------------------------------------------------------------------------- 1 | import { debug } from "./logger.js"; 2 | 3 | export default class PerfMetrics { 4 | constructor() { 5 | this._metrics = []; 6 | this._average = null; 7 | this._standardDeviation = null; 8 | } 9 | addMetric(metric) { 10 | const diff = metric.endTime - metric.startTime; 11 | this._metrics.push({ ...metric, diff }); 12 | } 13 | calcStandardDeviation() { 14 | if (this._average === null) { 15 | this.calcAverage(); 16 | } 17 | const average = this._average; 18 | const metricsLen = this._metrics.length; 19 | const sum = this._metrics.reduce((acc, curr) => { 20 | return acc + Math.pow(curr.diff - average, 2); 21 | }, 0); 22 | this._standardDeviation = Math.sqrt(sum / metricsLen); 23 | } 24 | calcAverage() { 25 | if (this._metrics.length > 1) { 26 | const sum = this._metrics.reduce((acc, curr, i) => { 27 | // Log progress every 10 iterations 28 | if (i % 10 === 0) { 29 | debug(`${i}/${this._metrics.length}`); 30 | } 31 | return acc + curr.diff; 32 | }, 0); 33 | this._average = sum / this._metrics.length; 34 | } else { 35 | this._average = this._metrics?.[0]?.diff; 36 | } 37 | } 38 | standardDeviation() { 39 | return this._standardDeviation; 40 | } 41 | average() { 42 | return this._average; 43 | } 44 | metrics() { 45 | return this._metrics; 46 | } 47 | metricsLen() { 48 | return this._metrics.length; 49 | } 50 | outLiers() { 51 | if (this._metrics.length === 0) { 52 | return []; 53 | } 54 | if (this._average === null) { 55 | this.calcAverage(); 56 | } 57 | if (this._standardDeviation === null) { 58 | this.calcStandardDeviation(); 59 | } 60 | const average = this._average; 61 | const standardDeviation = this._standardDeviation; 62 | const outLiers = this._metrics.filter( 63 | (metric) => metric.diff > average + standardDeviation 64 | ); 65 | return outLiers; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/events/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | buyRack as clientBuyRack, 3 | stake as clientStake, 4 | submitTask as clientSubmitTask, 5 | unstake as clientUnstake, 6 | } from "./client-events.js"; 7 | import { 8 | blockInvalidated, 9 | blockMined, 10 | blockValidated, 11 | buyRack, 12 | connect, 13 | stake, 14 | submitTask, 15 | taskValidated, 16 | unstake, 17 | updateChain, 18 | } from "./node-events.js"; 19 | import { ping } from "./utils.js"; 20 | 21 | export const clientEvents = { 22 | "buy-rack": clientBuyRack, 23 | ping: ping, 24 | stake: clientStake, 25 | "submit-task": clientSubmitTask, 26 | unstake: clientUnstake, 27 | }; 28 | 29 | export const nodeEvents = { 30 | connect: connect, 31 | "block-invalidated": blockInvalidated, 32 | "block-mined": blockMined, 33 | "block-validated": blockValidated, 34 | "buy-rack": buyRack, 35 | ping: ping, 36 | stake: stake, 37 | "submit-task": submitTask, 38 | "task-validated": taskValidated, 39 | unstake: unstake, 40 | "update-chain": updateChain, 41 | }; 42 | 43 | export async function handleClientEvent({ data, name, type }) { 44 | if (clientEvents[type]) { 45 | let parsed = data; 46 | try { 47 | parsed = JSON.parse(data); 48 | } catch (e) { 49 | // debug(`Error parsing JSON: ${data}`); 50 | } 51 | try { 52 | const res = await clientEvents[type](parsed, name); 53 | return res ?? 200; 54 | } catch (e) { 55 | return e; 56 | } 57 | } 58 | return "Invalid event type sent"; 59 | } 60 | 61 | export async function handleNodeEvent({ data, name, type }) { 62 | if (nodeEvents[type]) { 63 | let parsed = data; 64 | try { 65 | parsed = JSON.parse(data); 66 | } catch (e) { 67 | // debug("Error parsing Node JSON: ", e); 68 | } 69 | try { 70 | const res = await nodeEvents[type](parsed, name); 71 | return res; 72 | } catch (e) { 73 | // debug("Error handling event: ", e); 74 | return e; 75 | } 76 | } 77 | return "Invalid event type sent"; 78 | } 79 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/events/utils.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { nodeState, NAME } from "../state.js"; 3 | 4 | /** 5 | * Quiz data used in tasks 6 | */ 7 | const quiz = JSON.parse(fs.readFileSync("../assets/quiz.json", "utf8")); 8 | 9 | /** 10 | * Broadcasts an event to all peer Nodes including self 11 | */ 12 | export function broadcast({ data, name, type }) { 13 | nodeState.nodeSocks.forEach((sock) => { 14 | try { 15 | sock.send(parse({ data, name, type })); 16 | } catch (err) { 17 | error(err); 18 | } 19 | }); 20 | handleNodeEvent({ data, name, type }); 21 | } 22 | 23 | /** 24 | * Parses data from the `submit-task` event, and determines whether task has been successfully completed or not 25 | */ 26 | export function handleSubmitTask({ 27 | data: { task, orderNumberSelected }, 28 | name, 29 | }) { 30 | // Check if task is correct. 31 | const quizQ = quiz.find((q) => q.question === task.question); 32 | const result = Object.entries(quizQ.results).find(([key, val]) => 33 | val.includes(orderNumberSelected) 34 | )?.[0]; 35 | // Randomly decide if task has been correctly, incorrectly, or misbehavingly completed. 36 | const willCatchMisbehaviour = Math.random() > 0.2; 37 | return { 38 | taskValid: result === "correct" || !willCatchMisbehaviour, 39 | }; 40 | } 41 | 42 | /** 43 | * Pushes provided task to the node state 44 | */ 45 | export function addTaskToState(task) { 46 | nodeState.tasks.push(task); 47 | } 48 | 49 | /** 50 | * Randomly selects a quiz question from the global quiz data 51 | */ 52 | export function getRandomTask() { 53 | const randomIndex = Math.floor(Math.random() * quiz.length); 54 | const task = quiz[randomIndex]; 55 | return { question: task.question, options: task.options }; 56 | } 57 | 58 | /** 59 | * Accepts a proposed chain, and broadcasts a `block-mined` event with the proposed chain 60 | */ 61 | export function handleProposedBlock(proposedChain) { 62 | broadcast({ 63 | data: { chain: proposedChain }, 64 | name: NAME, 65 | type: "block-mined", 66 | }); 67 | } 68 | 69 | export function ping(data, name) { 70 | return "pong"; 71 | } 72 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tutorial/state.js: -------------------------------------------------------------------------------- 1 | import { chain, tasks, transactionPool } from "../tools/socket"; 2 | 3 | export const sampleChain = [ 4 | { 5 | data: [ 6 | { 7 | name: "Camper", 8 | racks: 8, 9 | reputation: 8, 10 | staked: 90, 11 | tokens: 100, 12 | }, 13 | ], 14 | }, 15 | ]; 16 | 17 | export const sampleTask = { 18 | question: "\nWhat's the correct way to display `Hello world`?\n", 19 | options: [ 20 | { 21 | code: '```js\nconsole.log("Hello world");\n```', 22 | order: 0, 23 | }, 24 | { 25 | code: '```py\nprint("Hello world")\n```', 26 | order: 1, 27 | }, 28 | { 29 | code: '```c\nprintf("Hello world");\n```', 30 | order: 2, 31 | }, 32 | { 33 | code: '```java\nSystem.out.println("Hello world");\n```', 34 | order: 3, 35 | }, 36 | { 37 | code: '```ruby\nputs "Hello world"\n```', 38 | order: 4, 39 | }, 40 | { 41 | code: "```php\n\n```", 42 | order: 5, 43 | }, 44 | ], 45 | }; 46 | 47 | const tutorialState = [...sampleChain]; 48 | 49 | export function handleTutorial(socket, { type }) { 50 | switch (type) { 51 | case "stake": 52 | tutorialState[0].data[0].staked += 1; 53 | socket.dispatchEvent(chain({ chain: tutorialState })); 54 | socket.dispatchEvent( 55 | transactionPool({ 56 | transactionPool: [{ event: "stake", name: "Camper" }], 57 | }) 58 | ); 59 | break; 60 | case "unstake": 61 | tutorialState[0].data[0].staked -= 1; 62 | socket.dispatchEvent(chain({ chain: tutorialState })); 63 | break; 64 | case "submit-task": 65 | socket.dispatchEvent(tasks({ tasks: [] })); 66 | break; 67 | case "buy-rack": 68 | tutorialState[0].data[0].racks += 1; 69 | tutorialState[0].data[0].tokens -= 10; 70 | socket.dispatchEvent(chain({ chain: tutorialState })); 71 | break; 72 | default: 73 | socket.dispatchEvent(chain({ chain: sampleChain })); 74 | socket.dispatchEvent(tasks({ tasks: [] })); 75 | break; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "build-a-smart-contract-in-rust/Cargo.toml", 4 | "learn-proof-of-stake-consensus-by-completing-a-web3-game/blockchain/Cargo.toml", 5 | "build-a-web3-client-side-package-for-your-dapp/fixture/blockchain/Cargo.toml" 6 | ], 7 | "files.exclude": { 8 | ".devcontainer.json": true, 9 | ".editorconfig": true, 10 | ".freeCodeCamp": true, 11 | ".git": true, 12 | ".github": true, 13 | ".gitignore": true, 14 | "*/.gitkeep": true, 15 | ".gitpod.Dockerfile": true, 16 | ".gitpod.yml": true, 17 | ".logs": true, 18 | ".prettierignore": true, 19 | ".prettierrc": true, 20 | ".vscode": true, 21 | "bash": true, 22 | "config": true, 23 | "curriculum": true, 24 | "tooling": true, 25 | "Dockerfile": true, 26 | "freecodecamp.conf.json": true, 27 | "node_modules": true, 28 | "package.json": true, 29 | "package-lock.json": true, 30 | "LICENSE": true, 31 | "README.md": true, 32 | "build-a-fundraising-smart-contract": true, 33 | "build-a-fundraising-smart-contract/.tests": true, 34 | "build-a-peer-to-peer-network": true, 35 | "build-a-smart-contract-in-rust": true, 36 | "build-a-video-game-marketplace-blockchain": true, 37 | "build-a-video-game-marketplace-blockchain/.tests": true, 38 | "build-a-web3-client-side-package-for-your-dapp": true, 39 | "learn-digital-ledgers-by-building-a-blockchain": true, 40 | "learn-proof-of-work-consensus-by-building-a-block-mining-algorithm": true, 41 | "learn-digital-signatures-by-building-a-wallet": true, 42 | "learn-basic-rust-by-building-a-cli-wallet": true, 43 | "learn-intermediate-rust-by-building-a-blockchain": true, 44 | "learn-proof-of-stake-consensus-by-completing-a-web3-game": true, 45 | "learn-smart-contracts-by-building-a-dumb-contract": true, 46 | "learn-websockets-by-building-a-blockchain-explorer": true 47 | }, 48 | "terminal.integrated.profiles.linux": { 49 | "bash": { 50 | "path": "bash", 51 | "icon": "terminal-bash", 52 | "args": ["--init-file", "./bash/sourcerer.sh"] 53 | } 54 | }, 55 | "terminal.integrated.defaultProfile.linux": "bash", 56 | "workbench.colorTheme": "freeCodeCamp Dark Theme" 57 | } 58 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/tools/draggable.js: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useEffect, useCallback } from "react"; 2 | 3 | const throttle = (f) => { 4 | let token = null, 5 | lastArgs = null; 6 | const invoke = () => { 7 | f(...lastArgs); 8 | token = null; 9 | }; 10 | const result = (...args) => { 11 | lastArgs = args; 12 | if (!token) { 13 | token = requestAnimationFrame(invoke); 14 | } 15 | }; 16 | result.cancel = () => token && cancelAnimationFrame(token); 17 | return result; 18 | }; 19 | 20 | const id = (x) => x; 21 | const useDraggable = ({ onDrag = id } = {}) => { 22 | const [pressed, setPressed] = useState(false); 23 | 24 | const position = useRef({ x: 0, y: 0 }); 25 | const ref = useRef(); 26 | 27 | const unsubscribe = useRef(); 28 | const legacyRef = useCallback((elem) => { 29 | ref.current = elem; 30 | if (unsubscribe.current) { 31 | unsubscribe.current(); 32 | } 33 | if (!elem) { 34 | return; 35 | } 36 | const handleMouseDown = (e) => { 37 | // don't forget to disable text selection during drag and drop 38 | // operations 39 | e.target.style.userSelect = "none"; 40 | setPressed(true); 41 | }; 42 | elem.addEventListener("mousedown", handleMouseDown); 43 | unsubscribe.current = () => { 44 | elem.removeEventListener("mousedown", handleMouseDown); 45 | }; 46 | }, []); 47 | 48 | useEffect(() => { 49 | if (!pressed) { 50 | return; 51 | } 52 | const handleMouseMove = throttle((event) => { 53 | if (!ref.current || !position.current) { 54 | return; 55 | } 56 | const pos = position.current; 57 | 58 | const elem = ref.current; 59 | position.current = onDrag({ 60 | x: pos.x + event.movementX, 61 | y: pos.y + event.movementY, 62 | }); 63 | elem.style.transform = `translate(${pos.x}px, ${pos.y}px)`; 64 | }); 65 | const handleMouseUp = (e) => { 66 | e.target.style.userSelect = "auto"; 67 | setPressed(false); 68 | }; 69 | document.addEventListener("mousemove", handleMouseMove); 70 | document.addEventListener("mouseup", handleMouseUp); 71 | return () => { 72 | handleMouseMove.cancel(); 73 | document.removeEventListener("mousemove", handleMouseMove); 74 | document.removeEventListener("mouseup", handleMouseUp); 75 | }; 76 | }, [pressed, onDrag]); 77 | 78 | return [legacyRef, pressed]; 79 | }; 80 | 81 | export default useDraggable; 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web3 Curriculum 2 | 3 | Nathan Flickinger, a freeCodeCamp alum who founded his own successful Web3 company, has donated $1M to freeCodeCamp, and asked that we use some of these funds to develop a carbon-neutral Web3 curriculum where you can learn by building more than a dozen projects. 4 | 5 | This free curriculum will teach the fundamentals of smart contracts and distributed application development. 6 | 7 | This will be a stand-alone curriculum that you can choose to tackle whenever you feel ready. The prerequisites will involve learning full stack web development through the first 7 freeCodeCamp certifications. (Though as with all aspects of freeCodeCamp, you're welcome to skip around.) 8 | Our goal is to help people learn these skills so they can get one of the thousands of open jobs that require these technologies, or start entrepreneurial projects of their own. 9 | 10 | ## Nathan's Story 11 | 12 | Nathan Flickinger was a college dropout. After a period of homelessness, he vowed to get his life together. It started with using his CompTIA certification he had earned in high school to get a minimum wage job in tech support. 13 | 14 | From there, Nathan decided to teach himself to code. After several months of study on freeCodeCamp, he was able to land his first software engineering job. 15 | 16 | He became obsessed with Web3 development, and eventually took the plunge and created his own startup. He helped write smart contract code for several Web3 projects. Most notably, KaijuKingz, a monster-inspired collection of cute lizards, each with quirky features. Instead of coffee, they drink radioactive sludge. 17 | 18 | People from the KaijuKingz community buy and trade these artistic works through a system called Non-fungible Tokens – essentially, deeds of ownership. 19 | 20 | Instead of being stored in a centralized database, these deeds are backed up in computers around the world through a distributed database. 21 | 22 | Early on, Nathan decided that if his project was successful, he wanted to donate a large amount of money to freeCodeCamp to help other people to learn how to harness technology to realize their career potential. 23 | 24 | And today, he and KaijuKingz have done that. They have donated a million dollars to our nonprofit, for us to use toward our mission of creating free learning resources for people around the world. 25 | 26 | We are extremely grateful for Nathan and his colleagues at KaijuKingz, and their willingness to support free, open source education. You can [read more about Nathan and his coding journey here](https://www.freecodecamp.org/news/donating-a-million-dollars-to-freecodecamp-a-web3-curriculum). 27 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ground-height: 100px; 3 | --server-width: 150px; 4 | --monitor-height: 250px; 5 | --server-border-width: 7px; 6 | --dark-1: #0a0a23; 7 | --dark-2: #1b1b32; 8 | --dark-3: #2a2a40; 9 | --dark-4: #3b3b4f; 10 | --light-1: #ffffff; 11 | --light-2: #f5f6f7; 12 | --light-3: #dfdfe2; 13 | --light-4: #d0d0d5; 14 | --light-purple: #dbb8ff; 15 | --light-yellow: #f1be32; 16 | --light-blue: #99c9ff; 17 | --light-green: #acd157; 18 | --dark-purple: #5a01a7; 19 | --dark-yellow: #ffac33; 20 | --dark-something: #4d3800; 21 | --dark-blue: #002ead; 22 | --dark-green: #00471b; 23 | } 24 | 25 | body, 26 | #root { 27 | width: 100%; 28 | height: 100vh; 29 | background-color: black; 30 | color: rgb(216, 216, 216); 31 | overflow: hidden; 32 | margin: 0; 33 | } 34 | 35 | .dark { 36 | background-color: black; 37 | } 38 | 39 | nav { 40 | width: 100%; 41 | height: 50px; 42 | background-color: #2c3e50; 43 | } 44 | 45 | main { 46 | width: 100%; 47 | height: calc(100vh - 50px); 48 | background-color: #111; 49 | display: flex; 50 | flex-direction: column; 51 | justify-content: space-between; 52 | } 53 | 54 | #ceiling { 55 | background-color: #333; 56 | width: 100%; 57 | height: 20px; 58 | display: flex; 59 | justify-content: center; 60 | } 61 | 62 | section.room { 63 | width: 100%; 64 | height: 100%; 65 | display: flex; 66 | flex-direction: column; 67 | justify-content: flex-end; 68 | align-items: center; 69 | } 70 | .station { 71 | display: flex; 72 | flex-direction: column; 73 | justify-content: flex-end; 74 | align-items: center; 75 | } 76 | 77 | #ground { 78 | background-color: #333; 79 | width: 100%; 80 | height: var(--ground-height); 81 | bottom: 0; 82 | } 83 | 84 | .show-take-over { 85 | animation: take-over 2s ease-in-out 0s 1; 86 | } 87 | 88 | @keyframes take-over { 89 | /* window shakes */ 90 | 0% { 91 | transform: rotate(0deg); 92 | } 93 | 10% { 94 | transform: rotate(1deg); 95 | } 96 | 20% { 97 | transform: rotate(-1deg); 98 | } 99 | 30% { 100 | transform: rotate(1deg); 101 | } 102 | 40% { 103 | transform: rotate(-1deg); 104 | } 105 | 50% { 106 | transform: rotate(1deg); 107 | } 108 | 60% { 109 | transform: rotate(-1deg); 110 | } 111 | 70% { 112 | transform: rotate(1deg); 113 | } 114 | 80% { 115 | transform: rotate(-1deg); 116 | } 117 | 90% { 118 | transform: rotate(1deg); 119 | } 120 | 100% { 121 | transform: rotate(0deg); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/server.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useContext } from "react"; 2 | import { dispatchStake, dispatchUnstake } from "../node-state"; 3 | import glow from "../tools/glow"; 4 | import { SocketContext } from "../tools/socket"; 5 | import "./server.css"; 6 | 7 | const greenTints = ["#00FF00", "#88FF00", "#00FF88", "#0DEE0D", "#18D418"]; 8 | 9 | const redTints = ["#FF0000", "#FF0088", "#FF8800", "#EE0D0D", "#D41818"]; 10 | 11 | const unstakedTint = "#aaa"; 12 | 13 | const animationConfig = { 14 | animationIterationCount: "infinite", 15 | animationTimingFunction: "linear", 16 | animationName: "blink", 17 | }; 18 | 19 | const Server = ({ 20 | serverData: { tasks = 0, tokens = 0, staked = 0 }, 21 | isLightOn, 22 | }) => { 23 | const socket = useContext(SocketContext); 24 | const [leds, setLeds] = useState([]); 25 | 26 | useEffect(() => { 27 | let tasksAssigned = 0; 28 | setLeds( 29 | [...Array(tokens).keys()].map((_, i) => { 30 | if (i >= staked) { 31 | return { 32 | animationDuration: Math.floor(Math.random() * 200) / 100 + 1 + "s", 33 | backgroundColor: unstakedTint, 34 | }; 35 | } 36 | let needsWork = false; 37 | const stakedTokensLeft = staked - i; 38 | const tasksLeftToAssign = tasks - tasksAssigned; 39 | if (tasksLeftToAssign !== 0 && tasksLeftToAssign === stakedTokensLeft) { 40 | needsWork = true; 41 | } else if (tasksLeftToAssign > 0) { 42 | needsWork = Math.floor(Math.random() * 2) === 1; 43 | } 44 | if (needsWork) { 45 | tasksAssigned++; 46 | } 47 | const backgroundColor = needsWork 48 | ? redTints[i % redTints.length] 49 | : greenTints[i % greenTints.length]; 50 | return { 51 | animationDuration: Math.floor(Math.random() * 200) / 100 + 1 + "s", 52 | backgroundColor, 53 | }; 54 | }) 55 | ); 56 | }, [tasks, staked, tokens]); 57 | 58 | return ( 59 |
60 | {leds.map((led, i) => ( 61 |
{ 65 | if (unstakedTint === led.backgroundColor) { 66 | dispatchStake(socket); 67 | } else { 68 | dispatchUnstake(socket); 69 | } 70 | }} 71 | style={{ 72 | ...animationConfig, 73 | ...led, 74 | ...glow(`.status-led:nth-of-type(${i + 1})`, isLightOn), 75 | }} 76 | >
77 | ))} 78 |
79 | ); 80 | }; 81 | 82 | export default Server; 83 | -------------------------------------------------------------------------------- /learn-digital-signatures-by-building-a-wallet/blockchain-helpers.js: -------------------------------------------------------------------------------- 1 | import sha256 from 'crypto-js/sha256.js'; 2 | import { writeFileSync, readFileSync } from 'fs'; 3 | 4 | export function writeBlockchain(blockchain) { 5 | const blockchainString = JSON.stringify(blockchain, null, 2); 6 | writeFileSync('./blockchain.json', blockchainString); 7 | } 8 | 9 | export function getBlockchain() { 10 | const blockchainFile = readFileSync('./blockchain.json'); 11 | const blockchain = JSON.parse(blockchainFile); 12 | return blockchain; 13 | } 14 | 15 | export function isValidChain() { 16 | const blockchain = getBlockchain(); 17 | 18 | // loop through blocks 19 | for (let i = 1; i < blockchain.length; i++) { 20 | const previousBlock = blockchain[i - 1]; 21 | const { hash, nonce, previousHash, transactions } = blockchain[i]; 22 | 23 | // validate previous hash 24 | if (previousHash !== previousBlock.hash) { 25 | return false; 26 | } 27 | 28 | // validate block hash 29 | const testBlockHash = sha256(nonce + previousBlock.hash + JSON.stringify(transactions)).toString(); 30 | if (hash != testBlockHash) { 31 | return false; 32 | } 33 | 34 | // loop through transactions 35 | for (let j = 0; j < transactions.length; j++) { 36 | const { hash, fromAddress, toAddress, amount } = transactions[j]; 37 | 38 | // don't validate reward transactions 39 | if (fromAddress !== null) { 40 | 41 | // validate transaction hash 42 | const testTransactionHash = sha256(fromAddress + toAddress + amount).toString(); 43 | if (hash != testTransactionHash) { 44 | return false; 45 | } 46 | 47 | } 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | 54 | export function writeTransactions(transactions) { 55 | const transactionsString = JSON.stringify(transactions, null, 2); 56 | writeFileSync('./transactions.json', transactionsString); 57 | } 58 | 59 | export function getTransactions() { 60 | const transactionsFile = readFileSync('./transactions.json'); 61 | const transactions = JSON.parse(transactionsFile); 62 | return transactions; 63 | } 64 | 65 | export function getAddressBalance(address) { 66 | const blockchain = getBlockchain(); 67 | let balance = 0; 68 | 69 | // loop through blocks 70 | for (let i = 1; i < blockchain.length; i++) { 71 | const { transactions } = blockchain[i]; 72 | 73 | // loop through transactions 74 | for (let j = 0; j < transactions.length; j++) { 75 | const { fromAddress, toAddress, amount } = transactions[j]; 76 | 77 | if (fromAddress === address) { 78 | balance -= amount; 79 | } 80 | 81 | if (toAddress === address) { 82 | balance += amount; 83 | } 84 | } 85 | } 86 | 87 | return balance; 88 | } -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/index.js: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import { handleClientWebSocket } from "./handle-client.js"; 5 | import { handleNodeWebsockets } from "./handle-node.js"; 6 | import { info } from "../utils/logger.js"; 7 | 8 | (async () => { 9 | await handleNodeWebsockets(); 10 | const portForClient = await handleClientWebSocket(); 11 | 12 | // Create http server 13 | const server = http.createServer((req, res) => { 14 | // res.setHeader("Access-Control-Allow-Origin", "*"); 15 | // res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); 16 | // res.setHeader("Access-Control-Allow-Methods", "GET"); 17 | res.setHeader("Access-Control-Allow-Headers", "Content-Type"); 18 | res.setHeader("Access-Control-Allow-Credentials", true); 19 | 20 | switch (req.url) { 21 | case "/port": 22 | res.writeHead(200, { "Content-Type": "application/json" }); 23 | return res.end(JSON.stringify({ portForClient })); 24 | default: 25 | // Do nothing 26 | break; 27 | } 28 | 29 | // debug(req.url); 30 | 31 | const baseToClient = `../client/dist`; 32 | let filePath = baseToClient + req.url; 33 | if (req.url === "/") { 34 | filePath = baseToClient + "/index.html"; 35 | } 36 | 37 | var extname = String(path.extname(filePath)).toLowerCase(); 38 | var mimeTypes = { 39 | ".html": "text/html", 40 | ".js": "text/javascript", 41 | ".css": "text/css", 42 | ".json": "application/json", 43 | ".png": "image/png", 44 | ".jpg": "image/jpg", 45 | ".gif": "image/gif", 46 | ".svg": "image/svg+xml", 47 | ".wav": "audio/wav", 48 | ".mp4": "video/mp4", 49 | ".woff": "application/font-woff", 50 | ".ttf": "application/font-ttf", 51 | ".eot": "application/vnd.ms-fontobject", 52 | ".otf": "application/font-otf", 53 | ".wasm": "application/wasm", 54 | }; 55 | 56 | var contentType = mimeTypes[extname] || "application/octet-stream"; 57 | 58 | fs.readFile(filePath, function (error, content) { 59 | if (error) { 60 | if (error.code == "ENOENT") { 61 | res.writeHead(404, { "Content-Type": "text/plain" }); 62 | res.end("404", "utf-8"); 63 | } else { 64 | res.writeHead(500); 65 | res.end( 66 | "Sorry, check with the site admin for error: " + 67 | error.code + 68 | " ..\n" 69 | ); 70 | } 71 | } else { 72 | res.writeHead(200, { "Content-Type": contentType }); 73 | res.end(content, "utf-8"); 74 | } 75 | }); 76 | }); 77 | server.listen(() => { 78 | const port = server.address().port; 79 | info(`Server listening on port ${port}`); 80 | info(`http://localhost:${port}`); 81 | }); 82 | })(); 83 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/bubbles.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "text": "Hello, Camper 👋. My name is Camperbot. Click the buttons above to learn more about me..." 4 | }, 5 | { 6 | "text": "I am your friendly assistant, and will help you with your work tasks." 7 | }, 8 | { 9 | "text": "My family are so proud for working with you, one of brightest blockchain developers of our time." 10 | }, 11 | { 12 | "text": "Counting all the lit up LEDs, I can see that you have 100 tokens!" 13 | }, 14 | { 15 | "text": "Seeing as 90 of these LEDs are greenish, that makes 90/100 of your tokens staked in your Proof of Stake blockchain." 16 | }, 17 | { 18 | "text": "I think you should stake more of your tokens to have a better chance of getting tasks. Click on one of the whiteish LEDs to stake it." 19 | }, 20 | { 21 | "text": "Also, staking more tokens increases the chance of you gaining reputation for a correctly completed task." 22 | }, 23 | { 24 | "text": "With reputation, you can buy more server racks to store your tokens." 25 | }, 26 | { 27 | "text": "Being a robot, I can count the number of tokens you have very quickly. Click on my hat to get your account stats, and access the menu." 28 | }, 29 | { 30 | "text": "Oooo! Look, Camper! A green LED has turned reddish, and your server screen is flashing! You have a new task!" 31 | }, 32 | { 33 | "text": "Let us click on the screen to see what task you have!" 34 | }, 35 | { 36 | "text": "Hmm... A quiz. Looks like a hard one. I hope you get it correct so we get a reward. Complete the quiz." 37 | }, 38 | { 39 | "text": "Now that you submitted it, we will have to wait for other nodes to validate your submission." 40 | }, 41 | { 42 | "text": "So... weird weather we have been having." 43 | }, 44 | { 45 | "text": "Finally! Your answer has been validated." 46 | }, 47 | { 48 | "text": "Looks like it has been accepted, and added to the blockchain!" 49 | }, 50 | { 51 | "text": "There is your reward! Another token, and more reputation!" 52 | }, 53 | { 54 | "text": "With 10 unstaked tokens, and an extra reputation, you can buy another server rack. Click the button to buy one!" 55 | }, 56 | { 57 | "text": "That brings your total to... Wait! My senses are picking something up on the network..." 58 | }, 59 | { 60 | "text": "What! Nooo! The network is being compromised! We are being hacked!" 61 | }, 62 | { 63 | "text": "Look out!" 64 | }, 65 | { 66 | "text": "What?! Your servers... where did they go? Someone else must have stolen them." 67 | }, 68 | { 69 | "text": "All I managed to save is enough tokens to buy one server rack, and stake a few more." 70 | }, 71 | { 72 | "text": "You will have to start from the beginning to get your blockchain back." 73 | }, 74 | { 75 | "text": "Start doing tasks, staking tokens, and buying racks to get back what you lost." 76 | } 77 | ] 78 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/utils/parser/index.js: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import yaml from "js-yaml"; 3 | import { error } from "../logger.js"; 4 | 5 | const NUM_QUIZZES = 10; 6 | const QUESTION_MARKER = "### Question"; 7 | // const OPTIONS_MARKER = "### Options"; 8 | // const RESULTS_MARKER = "### Results"; 9 | const NEXT_MARKER = `### `; 10 | 11 | /** 12 | * Gets all content within a quiz 13 | * @param {string} fileContent - The quiz file content 14 | * @param {number} quizNumber - The number of the quiz 15 | * @returns {string} The content of the quiz 16 | */ 17 | function getQuizFromFileContent(fileContent, quizNumber) { 18 | const quiz = fileContent.match( 19 | new RegExp(`## ${quizNumber}\n(.*?)\n## ${quizNumber + 1}`, "s") 20 | )?.[1]; 21 | return quiz; 22 | } 23 | 24 | /** 25 | * Gets the question of the quiz 26 | * @param {string} quiz - The quiz content 27 | * @returns {string} The question of the quiz 28 | */ 29 | function getQuizQuestion(quiz) { 30 | const question = quiz.match( 31 | new RegExp(`${QUESTION_MARKER}\n(.*?)\n(?=${NEXT_MARKER})`, "s") 32 | )?.[1]; 33 | return question; 34 | } 35 | 36 | /** 37 | * Gets the options of the quiz 38 | * @param {string} quiz - The quiz content 39 | * @returns {Object[]} An array of `{ code: string, order: number }` 40 | */ 41 | function getQuizOptions(quiz) { 42 | const optionsString = quiz.trim().split(NEXT_MARKER)?.[2]; 43 | const options = optionsString.match(/```[a-z]+\n.*?\n```/gms) ?? []; 44 | return options.reduce((acc, option, i) => { 45 | return [...acc, { code: option, order: i }]; 46 | }, []); 47 | } 48 | 49 | function getQuizResults(quiz) { 50 | const resultsString = quiz.trim().split(NEXT_MARKER)?.[3]; 51 | const results = resultsString.match(/(?<=```yml\n).*?(?=\n```)/ms)?.[0] ?? []; 52 | return results; 53 | } 54 | 55 | function parseResultsYaml(results) { 56 | const resultsYaml = yaml.load(results); 57 | return resultsYaml; 58 | } 59 | 60 | async function parseQuiz() { 61 | const FILE = "./assets/quiz.md"; 62 | const fileContent = await fs.readFile(FILE, "utf8"); 63 | const quizzes = []; 64 | for (let i = 1; i < NUM_QUIZZES + 1; i++) { 65 | const quizString = getQuizFromFileContent(fileContent, i); 66 | const results = getQuizResults(quizString); 67 | const resultsYaml = parseResultsYaml(results); 68 | 69 | const quiz = { 70 | question: getQuizQuestion(quizString), 71 | options: getQuizOptions(quizString), 72 | results: resultsYaml, 73 | }; 74 | quizzes.push(quiz); 75 | } 76 | return quizzes; 77 | } 78 | 79 | async function writeQuizToFile(quizObj) { 80 | const FILE_PATH = "./assets/quiz.json"; 81 | try { 82 | await fs.writeFile(FILE_PATH, JSON.stringify(quizObj), { 83 | flag: "w+", 84 | }); 85 | } catch (e) { 86 | error("Error writing quiz to file"); 87 | error(e); 88 | } 89 | } 90 | 91 | const quizObj = await parseQuiz(); 92 | 93 | writeQuizToFile(quizObj); 94 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/events/client-events.js: -------------------------------------------------------------------------------- 1 | import { nodeState } from "../state.js"; 2 | import { parse } from "../../utils/websockets/index.js"; 3 | 4 | /** 5 | * Emitted when a client node tries to buy a new server rack 6 | */ 7 | export function buyRack(data, name) { 8 | if (nodeState.isNextMiner()) { 9 | // Add transaction to pool 10 | nodeState.transactionPool.push({ 11 | event: "BuyRack", 12 | name: nodeState.name, 13 | }); 14 | nodeState.clientSocks.forEach((sock) => { 15 | sock.send( 16 | parse({ 17 | data: { 18 | chain: nodeState.chain, 19 | tasks: nodeState.tasks, 20 | transactionPool: nodeState.transactionPool, 21 | }, 22 | name, 23 | type: "update-chain", 24 | }) 25 | ); 26 | }); 27 | } else { 28 | broadcast({ data, name, type: "buy-rack" }); 29 | } 30 | } 31 | 32 | /** 33 | * Emitted when a client node tries to stake a token 34 | */ 35 | export function stake(data, name) { 36 | if (nodeState.isNextMiner()) { 37 | // Add transaction to pool 38 | nodeState.transactionPool.push({ 39 | event: "Stake", 40 | name: nodeState.name, 41 | }); 42 | nodeState.clientSocks.forEach((sock) => { 43 | sock.send( 44 | parse({ 45 | data: { 46 | chain: nodeState.chain, 47 | tasks: nodeState.tasks, 48 | transactionPool: nodeState.transactionPool, 49 | }, 50 | name, 51 | type: "update-chain", 52 | }) 53 | ); 54 | }); 55 | } else { 56 | broadcast({ data, name, type: "stake" }); 57 | } 58 | } 59 | 60 | /** 61 | * Emitted when a client node tries to unstake a token 62 | */ 63 | export function unstake(data, name) { 64 | if (nodeState.isNextMiner()) { 65 | // Add transaction to pool 66 | nodeState.transactionPool.push({ 67 | event: "Unstake", 68 | name: nodeState.name, 69 | }); 70 | nodeState.clientSocks.forEach((sock) => { 71 | sock.send( 72 | parse({ 73 | data: { 74 | chain: nodeState.chain, 75 | tasks: nodeState.tasks, 76 | transactionPool: nodeState.transactionPool, 77 | }, 78 | name, 79 | type: "update-chain", 80 | }) 81 | ); 82 | }); 83 | } else { 84 | broadcast({ data, name, type: "unstake" }); 85 | } 86 | } 87 | 88 | /** 89 | * Emitted when a client node submits a task 90 | * - Task is removed from node state 91 | * - A `submit-task` event is broadcast 92 | */ 93 | export function submitTask(data, name) { 94 | // Remove task from state 95 | nodeState.tasks = nodeState.tasks.filter( 96 | (task) => data.task.question !== task.question 97 | ); 98 | // Broadcast for validation 99 | broadcast({ data, name, type: "submit-task" }); 100 | } 101 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/monitor.css: -------------------------------------------------------------------------------- 1 | .monitor { 2 | width: 100%; 3 | min-width: 200px; 4 | aspect-ratio: 2 / 1; 5 | margin-bottom: var(--server-border-width); 6 | z-index: 1; 7 | } 8 | .screen { 9 | width: calc(100% - 8px); 10 | height: 80%; 11 | background-color: #111; 12 | border: black solid 4px; 13 | border-radius: 10px; 14 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 15 | } 16 | .screen:hover { 17 | cursor: pointer; 18 | } 19 | .actual-screen { 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | width: 100vw; 24 | height: 100vh; 25 | display: flex; 26 | 27 | flex-direction: column; 28 | align-items: center; 29 | word-wrap: wrap; 30 | z-index: 100; 31 | background-color: #111; 32 | color: var(--light-2); 33 | } 34 | .actual-screen > header { 35 | grid-area: header; 36 | width: 100%; 37 | height: 3em; 38 | background-color: var(--dark-2); 39 | } 40 | .actual-screen #logo { 41 | width: 50%; 42 | height: 85%; 43 | max-height: 100%; 44 | background-color: var(--dark-1); 45 | display: block; 46 | margin: 0 auto; 47 | padding: 0.4em; 48 | } 49 | .description { 50 | padding-top: 1rem; 51 | } 52 | .content { 53 | min-height: 100px; 54 | } 55 | 56 | ul.options { 57 | display: flex; 58 | flex-direction: column; 59 | justify-content: center; 60 | align-items: center; 61 | padding: 0.5rem 0; 62 | } 63 | li.options-item { 64 | width: 100%; 65 | background-color: var(--dark-2); 66 | list-style: none; 67 | padding: 0.5rem; 68 | margin: 0.3rem; 69 | } 70 | li.options-item.selected { 71 | border: var(--light-yellow) solid 2px; 72 | } 73 | li > label { 74 | display: flex; 75 | width: 100%; 76 | font-weight: 700; 77 | color: var(--light-2); 78 | flex-direction: row; 79 | align-items: center; 80 | } 81 | li > label > input { 82 | margin-right: 1rem; 83 | } 84 | 85 | .submit { 86 | margin: 0 auto; 87 | width: 100%; 88 | } 89 | .submit > button { 90 | width: 80%; 91 | max-width: 300px; 92 | display: block; 93 | margin: 0 auto; 94 | height: 100%; 95 | background-image: linear-gradient(var(--light-yellow), var(--dark-yellow)); 96 | border-width: 0.2em; 97 | border-color: var(--dark-yellow); 98 | font-size: 1.2em; 99 | font-weight: bold; 100 | color: var(--dark-1); 101 | cursor: pointer; 102 | margin-top: 0.8em; 103 | } 104 | .submit > button:hover { 105 | background-image: none; 106 | background-color: var(--light-yellow); 107 | } 108 | 109 | .flash { 110 | animation: flash 2s ease-in-out 1s infinite; 111 | } 112 | @keyframes flash { 113 | 0% { 114 | background-color: black; 115 | } 116 | 50% { 117 | background-color: #555; 118 | } 119 | 100% { 120 | background-color: black; 121 | } 122 | } 123 | .arm { 124 | width: 10%; 125 | height: 18%; 126 | background-color: #111; 127 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 128 | margin: 0 auto; 129 | } 130 | .stand { 131 | width: 100%; 132 | height: 2%; 133 | background-color: #111; 134 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 135 | margin: 0 auto; 136 | } 137 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/handle-node.js: -------------------------------------------------------------------------------- 1 | import WebSocket, { WebSocketServer } from "ws"; 2 | import { 3 | findPortWebSocketServerListens, 4 | parseBuffer, 5 | parse, 6 | findAvailablePort, 7 | } from "../utils/websockets/index.js"; 8 | import { info, error, warn, debug } from "../utils/logger.js"; 9 | import { initialise } from "../blockchain/pkg/blockchain.js"; 10 | import { nodeState, NAME } from "./state.js"; 11 | import { handleNodeEvent } from "./events/index.js"; 12 | import { addTaskToState, getRandomTask } from "./events/utils.js"; 13 | 14 | export async function handleNodeWebsockets() { 15 | // Find peers 16 | const peerPorts = await findPortWebSocketServerListens(WebSocket, { 17 | timeout: 400, 18 | startPort: 30000, 19 | endPort: 30100, 20 | numberOfPorts: 3, 21 | }); 22 | debug("peerPorts: ", peerPorts); 23 | if (!peerPorts.length) { 24 | // If no peers are found, then, as first node on network, initialise chain 25 | info("No peers found, initialising chain..."); 26 | const { chain } = initialise(NAME); 27 | debug(chain); 28 | nodeState.chain = chain; 29 | // Add self to network 30 | nodeState.network.add(NAME); 31 | addTaskToState(getRandomTask()); 32 | } 33 | // Connect to peers 34 | for (const peerPort of peerPorts) { 35 | const peerSocket = new WebSocket(`ws://localhost:${peerPort}`); 36 | // Connection opened 37 | peerSocket.on("open", () => { 38 | nodeState.nodeSocks.push(peerSocket); 39 | // Request to be added to the chain? 40 | peerSocket.send( 41 | parse({ 42 | name: NAME, 43 | type: "connect", 44 | data: {}, 45 | }) 46 | ); 47 | }); 48 | // Ask for latest chain 49 | peerSocket.on("message", async (requestData) => { 50 | const { data, name, type } = parseBuffer(requestData); 51 | debug(`[${type}] From peer (${name}): `); 52 | const res = await handleNodeEvent({ data, name, type }); 53 | // debug(res); 54 | }); 55 | peerSocket.on("error", (err) => { 56 | error(err); 57 | }); 58 | peerSocket.on("close", () => { 59 | warn(`Peer disconnected`); 60 | }); 61 | } 62 | 63 | // Create a server for future peers to connect to 64 | const availableServerPort = await findAvailablePort(30000, 30100); 65 | const nodeWebSocketServer = new WebSocketServer({ 66 | port: availableServerPort, 67 | }); 68 | info(`Listening for peers on port ${availableServerPort}`); 69 | 70 | nodeWebSocketServer.on("connection", (ws, req) => { 71 | ws.on("message", async (requestData) => { 72 | const { data, name, type } = parseBuffer(requestData); 73 | debug(`[${type}] From peer (${name}): `); 74 | const res = await handleNodeEvent({ data, name, type }); 75 | // debug(res); 76 | // sock(res, nodeState.name, "res"); 77 | }); 78 | 79 | sock({ chain: nodeState.chain }, nodeState.name, "update-chain"); 80 | 81 | function sock(data, name, type = {}) { 82 | ws.send(parse({ data, name, type })); 83 | } 84 | nodeState.nodeSocks.push(ws); 85 | }); 86 | nodeWebSocketServer.on("error", (err) => { 87 | error(err); 88 | nodeState.network.delete(NAME); 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/node_trace.1.log: -------------------------------------------------------------------------------- 1 | {"traceEvents":[{"pid":4894,"tid":4894,"ts":23422008787,"tts":3727,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"node"}},{"pid":4894,"tid":4894,"ts":23422008790,"tts":3732,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"16.13.0"}},{"pid":4894,"tid":4894,"ts":23422008791,"tts":3733,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":4894,"tid":4894,"ts":23422008903,"tts":3844,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"16.13.0","v8":"9.4.146.19-node.13","uv":"1.42.0","zlib":"1.2.11","brotli":"1.0.9","ares":"1.17.2","modules":"93","nghttp2":"1.45.1","napi":"8","llhttp":"6.0.4","openssl":"1.1.1l+quic","cldr":"39.0","icu":"69.1","tz":"2021a","unicode":"13.0","ngtcp2":"0.1.0-DEV","nghttp3":"0.1.0-DEV"},"arch":"x64","platform":"linux","release":{"name":"node","lts":"Gallium"}}}},{"pid":4894,"tid":4896,"ts":23422009044,"tts":104,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":4894,"tid":4897,"ts":23422009807,"tts":111,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4898,"ts":23422009849,"tts":92,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4899,"ts":23422009969,"tts":152,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4900,"ts":23422010020,"tts":94,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4894,"ts":23422008787,"tts":3727,"ph":"M","cat":"__metadata","name":"process_name","dur":0,"tdur":0,"args":{"name":"node"}},{"pid":4894,"tid":4894,"ts":23422008790,"tts":3732,"ph":"M","cat":"__metadata","name":"version","dur":0,"tdur":0,"args":{"node":"16.13.0"}},{"pid":4894,"tid":4894,"ts":23422008791,"tts":3733,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"JavaScriptMainThread"}},{"pid":4894,"tid":4894,"ts":23422008903,"tts":3844,"ph":"M","cat":"__metadata","name":"node","dur":0,"tdur":0,"args":{"process":{"versions":{"node":"16.13.0","v8":"9.4.146.19-node.13","uv":"1.42.0","zlib":"1.2.11","brotli":"1.0.9","ares":"1.17.2","modules":"93","nghttp2":"1.45.1","napi":"8","llhttp":"6.0.4","openssl":"1.1.1l+quic","cldr":"39.0","icu":"69.1","tz":"2021a","unicode":"13.0","ngtcp2":"0.1.0-DEV","nghttp3":"0.1.0-DEV"},"arch":"x64","platform":"linux","release":{"name":"node","lts":"Gallium"}}}},{"pid":4894,"tid":4896,"ts":23422009044,"tts":104,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"WorkerThreadsTaskRunner::DelayedTaskScheduler"}},{"pid":4894,"tid":4897,"ts":23422009807,"tts":111,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4898,"ts":23422009849,"tts":92,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4899,"ts":23422009969,"tts":152,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}},{"pid":4894,"tid":4900,"ts":23422010020,"tts":94,"ph":"M","cat":"__metadata","name":"thread_name","dur":0,"tdur":0,"args":{"name":"PlatformWorkerThread"}}]} -------------------------------------------------------------------------------- /tooling/camper-info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Provides command-line output of useful debugging information 3 | * @example 4 | * 5 | * ```bash 6 | * node tooling/camper-info.js --history --directory 7 | * ``` 8 | */ 9 | 10 | import { 11 | getProjectConfig, 12 | getConfig, 13 | getState 14 | } from '@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/env.js'; 15 | import __helpers from '@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/test-utils.js'; 16 | import { Logger } from 'logover'; 17 | import { readdir, readFile } from 'fs/promises'; 18 | import { join } from 'path'; 19 | import os from 'os'; 20 | 21 | const logover = new Logger({ level: 'debug', timestamp: null }); 22 | 23 | const FLAGS = process.argv; 24 | 25 | async function main() { 26 | try { 27 | const handleFlag = { 28 | '--history': printCommandHistory, 29 | '--directory': printDirectoryTree 30 | }; 31 | const projectConfig = await getProjectConfig(); 32 | const config = await getConfig(); 33 | const state = await getState(); 34 | 35 | const { currentProject } = state; 36 | const { currentLesson } = projectConfig; 37 | const { version } = config; 38 | 39 | const devContainerFile = await readFile( 40 | '.devcontainer/devcontainer.json', 41 | 'utf-8' 42 | ); 43 | const devConfig = JSON.parse(devContainerFile); 44 | const coursesVersion = devConfig.extensions?.find(e => 45 | e.match('freecodecamp-courses') 46 | ); 47 | 48 | const { stdout } = await __helpers.getCommandOutput('git log -1 --oneline'); 49 | 50 | const osInfo = ` 51 | Architecture: ${os.arch()} 52 | Platform: ${os.platform()} 53 | Release: ${os.release()} 54 | Type: ${os.type()} 55 | `; 56 | 57 | logover.info('Project: ', currentProject); 58 | logover.info('Lesson Number: ', currentLesson); 59 | logover.info('Curriculum Version: ', version); 60 | logover.info('freeCodeCamp - Courses: ', coursesVersion); 61 | logover.info('Commit: ', stdout); 62 | logover.info('OS Info:', osInfo); 63 | 64 | for (const arg of FLAGS) { 65 | await handleFlag[arg]?.(); 66 | } 67 | async function printDirectoryTree() { 68 | const files = await readdir('.', { withFileTypes: true }); 69 | let depth = 0; 70 | for (const file of files) { 71 | if (file.isDirectory() && file.name === currentProject) { 72 | await recurseDirectory(file.name, depth); 73 | } 74 | } 75 | } 76 | 77 | async function printCommandHistory() { 78 | const historyCwd = await readFile('.logs/.history_cwd.log', 'utf-8'); 79 | logover.info('Command History:\n', historyCwd); 80 | } 81 | } catch (e) { 82 | logover.error(e); 83 | } 84 | } 85 | 86 | main(); 87 | 88 | const IGNORE = [ 89 | 'node_modules', 90 | 'target', 91 | 'test-ledger', 92 | 'store', 93 | '.cargo', 94 | '.DS_Store' 95 | ]; 96 | async function recurseDirectory(path, depth) { 97 | logover.info(`|${' '.repeat(depth * 2)}|-- ${path}`); 98 | depth++; 99 | const files = await readdir(path, { withFileTypes: true }); 100 | for (const file of files) { 101 | if (!IGNORE.includes(file.name)) { 102 | if (file.isDirectory()) { 103 | await recurseDirectory(join(path, file.name), depth); 104 | } else { 105 | logover.info(`|${' '.repeat(depth * 2)}|-- ${file.name}`); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/utils/websockets/index.js: -------------------------------------------------------------------------------- 1 | import PerfMetrics from "../perf-metrics.js"; 2 | import { debug, error, info } from "../logger.js"; 3 | 4 | const perfMetrics = new PerfMetrics(); 5 | 6 | export async function findPortWebSocketServerListens( 7 | WebSocketConstructor, 8 | { timeout = 1500, startPort = 30000, endPort = 65000, numberOfPorts = 1 } = {} 9 | ) { 10 | const listeningPorts = []; 11 | let batchSize = 10; 12 | let batch = 0; 13 | const opening = startPort; 14 | const closing = endPort - batchSize; 15 | 16 | while (batch * batchSize + opening < closing) { 17 | try { 18 | const port = await new Promise(async (resolve, reject) => { 19 | // Ping websockets until response 20 | const start = opening + batch * batchSize; 21 | const end = start + batchSize; 22 | let closedSockets = 0; 23 | for (let i = start; i < end; i++) { 24 | let endTime = 0; 25 | const socket = new WebSocketConstructor(`ws://localhost:${i}`); 26 | let startTime = performance.now(); 27 | 28 | const time = setTimeout(() => { 29 | socket.close(); 30 | }, timeout); 31 | socket.onopen = (event) => { 32 | socket.send( 33 | parse({ 34 | data: null, 35 | name: process.env.NAME || "anon", 36 | type: "ping", 37 | }) 38 | ); 39 | socket.close(); 40 | clearTimeout(time); 41 | listeningPorts.push(i); 42 | if (numberOfPorts === listeningPorts.length) { 43 | return resolve(listeningPorts); 44 | } 45 | }; 46 | socket.onerror = (_event) => { 47 | socket.close(); 48 | clearTimeout(time); 49 | }; 50 | socket.onclose = (_event) => { 51 | endTime = performance.now(); 52 | perfMetrics.addMetric({ 53 | id: i, 54 | startTime, 55 | endTime, 56 | }); 57 | closedSockets++; 58 | }; 59 | } 60 | const interval = setInterval(() => { 61 | // Check if all sockets are closed 62 | if (closedSockets >= end - start) { 63 | clearInterval(interval); 64 | return reject("No port found: " + start + " - " + end); 65 | } 66 | // Poll every 10ms 67 | }, 10); 68 | }); 69 | info("Found port: ", port); 70 | if (numberOfPorts === listeningPorts.length) { 71 | perfMetrics.calcAverage(); 72 | debug(`Average time socket lived: ${perfMetrics.average()}`); 73 | return listeningPorts; 74 | } 75 | batch++; 76 | } catch (e) { 77 | batch++; 78 | debug(e); 79 | } 80 | } 81 | perfMetrics.calcAverage(); 82 | debug(`Average time socket lived: ${perfMetrics.average()}`); 83 | return listeningPorts; 84 | } 85 | 86 | export function parse(obj) { 87 | return JSON.stringify(obj); 88 | } 89 | 90 | export function parseBuffer(buf) { 91 | return JSON.parse(buf.toString()); 92 | } 93 | 94 | export async function findAvailablePort(startPort, endPort) { 95 | try { 96 | const availablePort = await new Promise(async (resolve, reject) => { 97 | for (let port = startPort; port < endPort; port++) { 98 | const server = (await import("net")).createServer(); 99 | server.listen(port); 100 | server.on("listening", () => { 101 | server.close(); 102 | return resolve(port); 103 | }); 104 | server.on("error", (err) => { 105 | if (err.code === "EADDRINUSE") { 106 | debug(`Port in use: ${port}`); 107 | } else { 108 | error(err); 109 | } 110 | }); 111 | } 112 | }); 113 | return availablePort; 114 | } catch (err) { 115 | error(err); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /curriculum/locales/english/learn-intermediate-rust-by-building-a-blockchain.md: -------------------------------------------------------------------------------- 1 | # Web3 - Learn Intermediate Rust by Building a Blockchain 2 | 3 | ## 1 4 | 5 | ### --description-- 6 | 7 | You will build a Proof of Stake blockchain using Rust. 8 | 9 | This project comes with a boilerplate including the following: 10 | 11 | - `/blockchain` - the only directory you will be working in 12 | - `/client` - the directory containing the code for the clientside code 13 | - `/node` - the directory containing the server-side code managing a node in the network 14 | 15 | **Commands** 16 | 17 | - Run all unit tests: `cargo test --lib` 18 | - Run all integration tests: `wasm-pack test --chrome` or `wasm-pack test --firefox --headless` 19 | 20 | **Useful Resources** 21 | 22 | - [Rust Course](https://www.freecodecamp.org/news/rust-in-replit/) 23 | - [Rust and WASM](https://rustwasm.github.io/docs/book/) 24 | 25 | **Types** 26 | 27 | ```typescript 28 | type Account = { 29 | address: string; 30 | staked: number; 31 | tokens: number; 32 | } 33 | 34 | type Block = { 35 | data: Account[], 36 | hash: string, 37 | id: number, 38 | nonce: number, 39 | previous_hash: string, 40 | timestamp: number, 41 | next_miner: Account['address'], 42 | next_validators: Account['address'][], 43 | } 44 | 45 | type Chain = Block[]; 46 | 47 | enum Event = { 48 | AddAccount = "AddAccount", 49 | Punish = "Punish", 50 | Reward= "Reward", 51 | Stake = "Stake", 52 | Unstake = "Unstake", 53 | } 54 | 55 | type Transfer = { 56 | Transfer: [string, number] 57 | } 58 | 59 | type NodeState = { 60 | chain: Chain; 61 | network: Account['address'][]; 62 | transaction_pool: Transaction[]; 63 | } 64 | 65 | type Transaction = { 66 | address: Account['address']; 67 | event: Event | Transfer; 68 | } 69 | ``` 70 | 71 | **User Stories:** 72 | 73 | - Your blockchain uses Proof of Stake consensus 74 | - Your blockchain uses `wasm-pack` to compile the Rust code to JavaScript for Nodejs 75 | - Your blockchain exports an `initialise_chain` function that returns `Result` 76 | - Your blockchain exports a `mine_block` function that returns `Result` 77 | - This function accepts a `JsValue` with the `NodeState` type 78 | - Your blockchain exports a `validate_block` function that returns `Result` 79 | - This function accepts a `JsValue` with the `Block[]` type 80 | - Your blockchain passes all unit and integration tests 81 | 82 | ### --tests-- 83 | 84 | Your `blockchain` library should pass all `account::tests` unit tests. 85 | 86 | ```js 87 | const { stdout } = await __helpers.getCommandOutput( 88 | 'cargo test --lib account::tests', 89 | 'blockchain' 90 | ); 91 | assert.match(stdout, /test result: ok/); 92 | ``` 93 | 94 | Your `blockchain` library should pass all `block::tests` unit tests. 95 | 96 | ```js 97 | const { stdout } = await __helpers.getCommandOutput( 98 | 'cargo test --lib block::tests', 99 | 'blockchain' 100 | ); 101 | assert.match(stdout, /test result: ok/); 102 | ``` 103 | 104 | Your `blockchain` library should pass all `chain::tests` unit tests. 105 | 106 | ```js 107 | const { stdout } = await __helpers.getCommandOutput( 108 | 'cargo test --lib chain::tests', 109 | 'blockchain' 110 | ); 111 | assert.match(stdout, /test result: ok/); 112 | ``` 113 | 114 | Your `blockchain` library should pass all `lib::tests` unit tests. 115 | 116 | ```js 117 | const { stdout } = await __helpers.getCommandOutput( 118 | 'cargo test --lib lib::tests', 119 | 'blockchain' 120 | ); 121 | assert.match(stdout, /test result: ok/); 122 | ``` 123 | 124 | Your `blockchain` library should pass all `mine_block` integration tests. 125 | 126 | ```js 127 | // Execute `wasm-pack test --firefox --headless -- --test mine_block`, and pipe output to tests client 128 | const { stdout } = await __helpers.getCommandOutput( 129 | 'wasm-pack test --firefox --headless -- --test mine_block', 130 | 'blockchain' 131 | ); 132 | assert.match(stdout, /test result: ok/); 133 | ``` 134 | 135 | Your `blockchain` library should pass all `validate_block` integration tests. 136 | 137 | ```js 138 | const { stdout } = await __helpers.getCommandOutput( 139 | 'wasm-pack test --firefox --headless -- --test validate_block', 140 | 'blockchain' 141 | ); 142 | assert.match(stdout, /test result: ok/); 143 | ``` 144 | 145 | ## 2 146 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/assets/quiz.json: -------------------------------------------------------------------------------- 1 | [{"question":"\nWhat's the correct way to display `Hello world`?\n","options":[{"code":"```js\nconsole.log(\"Hello world\");\n```","order":0},{"code":"```py\nprint(\"Hello world\")\n```","order":1},{"code":"```c\nprintf(\"Hello world\");\n```","order":2},{"code":"```java\nSystem.out.println(\"Hello world\");\n```","order":3},{"code":"```ruby\nputs \"Hello world\"\n```","order":4},{"code":"```php\n\n```","order":5}],"results":{"correct":[0,1,2,3,4],"incorrect":[],"misbehaved":[5]}},{"question":"\nHow do you remove a table named `freeCodeCamp` using SQL?\n","options":[{"code":"```sql\nREMOVE TABLE freeCodeCamp;\n```","order":0},{"code":"```sql\nDROP TABLE freeCodeCamp;\n```","order":1},{"code":"```sql\nDESTROY TABLE freeCodeCamp;\n```","order":2},{"code":"```sql\n🧨TABLE freeCodeCamp;\n```","order":3},{"code":"```sql\nRobert'); DROP TABLE students;--\n```","order":4}],"results":{"correct":[1],"incorrect":[0,2],"misbehaved":[3,4]}},{"question":"\nIn HTML, how do you create an `h2` element that says `CatPhotoApp`?\n","options":[{"code":"```html\n

CatPhotoApp

\n```","order":0},{"code":"```html\nCatPhotoApp\n```","order":1},{"code":"```html\nh2 = \"CatPhotoApp\"\n```","order":2},{"code":"```html\n

CatPhotoApp

\n```","order":3},{"code":"```text\nWould rather make a dog photo app instead\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3],"misbehaved":[4]}},{"question":"\nWhat is the correct way to create a function in Python?\n","options":[{"code":"```py\ndef myFunction():\n```","order":0},{"code":"```py\nfunction myfunction():\n```","order":1},{"code":"```py\ncreate myFunction():\n```","order":2},{"code":"```py\nsudo myFunction():\n```","order":3},{"code":"```text\nHire a freelancer to do it\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3],"misbehaved":[4]}},{"question":"\nWhich symbols are used for a comment in JavaScript?\n","options":[{"code":"```js\n//\n```","order":0},{"code":"```js\n\\\\\n```","order":1},{"code":"```js\n/* *\\\n```","order":2},{"code":"```js\n\\* *\\\n```","order":3},{"code":"```text\n¯\\_(ツ)_/¯\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3],"misbehaved":[4]}},{"question":"\nWhat will display on a website with the following HTML/JavaScript?\n\n```html\n

\n\n```\n","options":[{"code":"```text\n50\n```","order":0},{"code":"```text\n10\n```","order":1},{"code":"```text\n5\n```","order":2},{"code":"```text\nblank\n```","order":3},{"code":"```text\nundefined\n```","order":4},{"code":"```text\nI'm too busy learning Web3 to think about this\n```","order":5}],"results":{"correct":[0],"incorrect":[1,2,3,4],"misbehaved":[5]}},{"question":"\nWhich does JavaScript interpreter ignore?\n","options":[{"code":"```text\nSpaces\n```","order":0},{"code":"```text\nTabs\n```","order":1},{"code":"```text\nNew lines\n```","order":2},{"code":"```text\nAll of the above\n```","order":3},{"code":"```text\nEvery 3rd semicolon\n```","order":4},{"code":"```text\nMost of my code\n```","order":5}],"results":{"correct":[0,1,2,3],"incorrect":[4],"misbehaved":[5]}},{"question":"\nWhich will print the digits 0 through 9, using Python?\n","options":[{"code":"```py\nprint(\"0123456789\")\n```","order":0},{"code":"```py\nfor i in range(9): print(i)\n```","order":1},{"code":"```py\nfor (let i; i < 10; i++) { console.log(i) }\n```","order":2},{"code":"```py\nfor i in range(10): print(\"i\")\n```","order":3},{"code":"```py\nfor i in 0::9: print(i)\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3,4],"misbehaved":[]}},{"question":"\nIf `f(x) = 4x^3 - 4x^2 + 10`, and `f(-2) = a`, what is `a`?\n","options":[{"code":"```text\n26\n```","order":0},{"code":"```text\n-38\n```","order":1},{"code":"```text\n10\n```","order":2},{"code":"```text\n38\n```","order":3},{"code":"```text\nNone of the above\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3],"misbehaved":[4]}},{"question":"\nWhat is the output of the following Python code?\n\n```py\nlistOne = [20, 40, 60, 80]\nlistTwo = [20, 40, 60, 80]\nprint(listOne == listTwo, listOne is listTwo)\n```\n","options":[{"code":"```py\nTrue False\n```","order":0},{"code":"```py\nFalse True\n```","order":1},{"code":"```py\nTrue True\n```","order":2},{"code":"```py\nFalse False\n```","order":3},{"code":"```text\nWhat did True say to False?\nStop boolean me 😆\n```","order":4}],"results":{"correct":[0],"incorrect":[1,2,3],"misbehaved":[4]}}] -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/camperbot.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import "./camperbot.css"; 3 | import glow from "../tools/glow"; 4 | import { dispatchBuyRack, getSelf, NameContext } from "../node-state"; 5 | import { SocketContext } from "../tools/socket"; 6 | 7 | const Camperbot = ({ 8 | text, 9 | setText, 10 | isLightOn, 11 | handleNextBubble, 12 | handlePreviousBubble, 13 | }) => { 14 | const socket = useContext(SocketContext); 15 | const name = useContext(NameContext); 16 | const [isShowBubble, setIsShowBubble] = useState(true); 17 | const [typewriter, setTypewriter] = useState(text[0] || ""); 18 | const [self, setSelf] = useState(null); 19 | 20 | useEffect(() => { 21 | if (socket) { 22 | socket.addEventListener("chain", ({ detail: { chain } }) => { 23 | setSelf(getSelf(name, chain)); 24 | }); 25 | } 26 | }, [socket, name]); 27 | 28 | const toggleBubble = () => { 29 | setIsShowBubble(!isShowBubble); 30 | }; 31 | 32 | const handleStats = (e) => { 33 | e.preventDefault(); 34 | e.stopPropagation(); 35 | if (!isShowBubble) { 36 | toggleBubble(); 37 | } 38 | 39 | const { tokens, staked, reputation } = self; 40 | const statText = `Tokens: ${tokens}\nStaked: ${staked}\nReputation: ${reputation}`; 41 | setText(statText); 42 | }; 43 | 44 | useEffect(() => { 45 | if (isShowBubble && self) { 46 | const { tokens, staked, reputation } = self; 47 | const statText = `Tokens: ${tokens}\nStaked: ${staked}\nReputation: ${reputation}`; 48 | setText(statText); 49 | } 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | }, [self]); 52 | 53 | useEffect(() => { 54 | let c = 0; 55 | const interval = setInterval(() => { 56 | if (c <= text.length) { 57 | setTypewriter(text.slice(0, c)); 58 | c++; 59 | } else { 60 | return clearInterval(interval); 61 | } 62 | }, 20); 63 | }, [text]); 64 | 65 | const handleRackPurchase = () => { 66 | dispatchBuyRack(socket); 67 | }; 68 | 69 | return ( 70 |
{ 73 | e.stopPropagation(); 74 | toggleBubble(); 75 | }} 76 | > 77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
93 |
94 |
95 |
96 | {isShowBubble && text && ( 97 |
98 |
99 | 107 |
108 | 116 | 124 |
125 |
{typewriter}
126 |
127 |
128 | )} 129 |
130 |
131 |
132 |
CAMPERBOT
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | ); 141 | }; 142 | 143 | export default Camperbot; 144 | -------------------------------------------------------------------------------- /tooling/helpers.js: -------------------------------------------------------------------------------- 1 | import __helpers from '@freecodecamp/freecodecamp-os/.freeCodeCamp/tooling/test-utils.js'; 2 | import sha256 from 'crypto-js/sha256.js'; 3 | import elliptic from 'elliptic'; 4 | import WebSocket, { WebSocketServer } from 'ws'; 5 | import { Babeliser } from 'babeliser'; 6 | 7 | const ec = new elliptic.ec('p192'); 8 | 9 | export async function babeliser(codeString) { 10 | return new Babeliser(codeString); 11 | } 12 | 13 | export function generateHash(content) { 14 | const hash = sha256(content).toString(); 15 | return hash; 16 | } 17 | 18 | export function generateSignature(privateKey, content) { 19 | const keyPair = ec.keyFromPrivate(privateKey, 'hex'); 20 | const signature = keyPair.sign(content).toDER('hex'); 21 | return signature; 22 | } 23 | 24 | export function validateSignature(publicKey, content, signature) { 25 | const keyPair = ec.keyFromPublic(publicKey, 'hex'); 26 | const verifiedSignature = keyPair.verify(content, signature); 27 | return verifiedSignature; 28 | } 29 | 30 | export function getPublicKeyFromPrivate(privateKey) { 31 | const keyPair = ec.keyFromPrivate(privateKey, 'hex'); 32 | const publicKey = keyPair.getPublic('hex'); 33 | return publicKey; 34 | } 35 | 36 | // used in fundraising contract project 37 | export async function getContract(contractAddress, cwd, includePool = true) { 38 | // get the latest contract state from the blockchain 39 | const blockchain = await __helpers.getJsonFile(`${cwd}/blockchain.json`); 40 | const latestContract = blockchain.reduce((currentContract, nextBlock) => { 41 | if (nextBlock.smartContracts) { 42 | nextBlock.smartContracts.forEach(contract => { 43 | if (contract.address === contractAddress) { 44 | // first occurrence of contract 45 | if (!currentContract.hasOwnProperty('address')) { 46 | Object.keys(contract).forEach( 47 | key => (currentContract[key] = contract[key]) 48 | ); 49 | 50 | // contract found and added, only update state after that 51 | } else if (contract.hasOwnProperty('state')) { 52 | currentContract.state = contract.state; 53 | } 54 | } 55 | }); 56 | } 57 | return currentContract; 58 | }, {}); 59 | 60 | if (includePool) { 61 | // add contract pool to latest contract state 62 | const smartContracts = await __helpers.getJsonFile( 63 | `${cwd}/smart-contracts.json` 64 | ); 65 | smartContracts.forEach(contract => { 66 | if (contract.address === contractAddress) { 67 | if (!latestContract.hasOwnProperty('address')) { 68 | Object.keys(contract).forEach( 69 | key => (latestContract[key] = contract[key]) 70 | ); 71 | } else if (latestContract.hasOwnProperty('state')) { 72 | latestContract.state = contract.state; 73 | } 74 | } 75 | }); 76 | } 77 | 78 | return latestContract.hasOwnProperty('address') ? latestContract : null; 79 | } 80 | 81 | // for p2p network project 82 | export async function canConnectToSocket(address) { 83 | return await new Promise(resolve => { 84 | const socket = new WebSocket(address, { shouldKeepAlive: false }); 85 | socket.on('open', () => { 86 | socket.close(); 87 | resolve(true); 88 | }); 89 | socket.on('error', () => resolve(false)); 90 | }); 91 | } 92 | 93 | export async function startSocketServerAndHandshake({ 94 | myPort: port, 95 | theirAddress = 'ws://localhost:4001', 96 | connectOnly = false 97 | }) { 98 | return await new Promise(resolve => { 99 | const address = `ws://localhost:${port}`; 100 | 101 | const server = new WebSocketServer({ port }); 102 | server.on('connection', externalSocket => { 103 | if (connectOnly) { 104 | externalSocket.close(); 105 | server.close(); 106 | resolve(true); 107 | } 108 | 109 | externalSocket.on('message', messageString => { 110 | const message = JSON.parse(messageString); 111 | 112 | if (message.hasOwnProperty('type') && message.type === 'HANDSHAKE') { 113 | externalSocket.close(); 114 | server.close(); 115 | resolve(message); 116 | } else { 117 | externalSocket.close(); 118 | server.close(); 119 | } 120 | }); 121 | }); 122 | 123 | setTimeout(() => { 124 | server.close(); 125 | resolve(); 126 | }, 5000); 127 | 128 | server.on('error', () => server.close()); 129 | 130 | const socket = new WebSocket(theirAddress, { shouldKeepAlive: false }); 131 | socket.on('open', () => { 132 | socket.send(JSON.stringify({ type: 'HANDSHAKE', data: [address] })); 133 | socket.close(); 134 | }); 135 | 136 | socket.on('error', () => resolve()); 137 | }); 138 | } 139 | -------------------------------------------------------------------------------- /bash/.bashrc: -------------------------------------------------------------------------------- 1 | # ~/.bashrc: executed by bash(1) for non-login shells. 2 | # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) 3 | # for examples 4 | 5 | # If not running interactively, don't do anything 6 | case $- in 7 | *i*) ;; 8 | *) return;; 9 | esac 10 | 11 | # don't put duplicate lines or lines starting with space in the history. 12 | # See bash(1) for more options 13 | HISTCONTROL=ignoreboth 14 | 15 | # append to the history file, don't overwrite it 16 | shopt -s histappend 17 | 18 | # for setting history length see HISTSIZE and HISTFILESIZE in bash(1) 19 | HISTSIZE=1000 20 | HISTFILESIZE=2000 21 | 22 | # check the window size after each command and, if necessary, 23 | # update the values of LINES and COLUMNS. 24 | shopt -s checkwinsize 25 | 26 | # If set, the pattern "**" used in a pathname expansion context will 27 | # match all files and zero or more directories and subdirectories. 28 | #shopt -s globstar 29 | 30 | # make less more friendly for non-text input files, see lesspipe(1) 31 | [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" 32 | 33 | # set variable identifying the chroot you work in (used in the prompt below) 34 | if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then 35 | debian_chroot=$(cat /etc/debian_chroot) 36 | fi 37 | 38 | # set a fancy prompt (non-color, unless we know we "want" color) 39 | case "$TERM" in 40 | xterm-color|*-256color) color_prompt=yes;; 41 | esac 42 | 43 | # uncomment for a colored prompt, if the terminal has the capability; turned 44 | # off by default to not distract the user: the focus in a terminal window 45 | # should be on the output of commands, not on the prompt 46 | #force_color_prompt=yes 47 | 48 | if [ -n "$force_color_prompt" ]; then 49 | if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then 50 | # We have color support; assume it's compliant with Ecma-48 51 | # (ISO/IEC-6429). (Lack of such support is extremely rare, and such 52 | # a case would tend to support setf rather than setaf.) 53 | color_prompt=yes 54 | else 55 | color_prompt= 56 | fi 57 | fi 58 | 59 | if [ "$color_prompt" = yes ]; then 60 | PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 61 | else 62 | PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 63 | fi 64 | unset color_prompt force_color_prompt 65 | 66 | # If this is an xterm set the title to user@host:dir 67 | case "$TERM" in 68 | xterm*|rxvt*) 69 | PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" 70 | ;; 71 | *) 72 | ;; 73 | esac 74 | 75 | # enable color support of ls and also add handy aliases 76 | if [ -x /usr/bin/dircolors ]; then 77 | test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" 78 | alias ls='ls --color=auto' 79 | #alias dir='dir --color=auto' 80 | #alias vdir='vdir --color=auto' 81 | 82 | alias grep='grep --color=auto' 83 | alias fgrep='fgrep --color=auto' 84 | alias egrep='egrep --color=auto' 85 | fi 86 | 87 | # colored GCC warnings and errors 88 | #export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' 89 | 90 | # some more ls aliases 91 | alias ll='ls -alF' 92 | alias la='ls -A' 93 | alias l='ls -CF' 94 | 95 | # Add an "alert" alias for long running commands. Use like so: 96 | # sleep 10; alert 97 | alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' 98 | 99 | # Alias definitions. 100 | # You may want to put all your additions into a separate file like 101 | # ~/.bash_aliases, instead of adding them here directly. 102 | # See /usr/share/doc/bash-doc/examples in the bash-doc package. 103 | 104 | if [ -f ~/.bash_aliases ]; then 105 | . ~/.bash_aliases 106 | fi 107 | 108 | # enable programmable completion features (you don't need to enable 109 | # this, if it's already enabled in /etc/bash.bashrc and /etc/profile 110 | # sources /etc/bash.bashrc). 111 | if ! shopt -oq posix; then 112 | if [ -f /usr/share/bash-completion/bash_completion ]; then 113 | . /usr/share/bash-completion/bash_completion 114 | elif [ -f /etc/bash_completion ]; then 115 | . /etc/bash_completion 116 | fi 117 | fi 118 | 119 | PS1='\[\]\u\[\] \[\]\w\[\]$(__git_ps1 " (%s)") $ ' 120 | 121 | for i in $(ls -A $HOME/.bashrc.d/); do source $HOME/.bashrc.d/$i; done 122 | 123 | . "$HOME/.cargo/env" 124 | 125 | # freeCodeCamp - Needed for most tests to work 126 | 127 | WD=/workspace/web3-curriculum 128 | 129 | PROMPT_COMMAND='>| $WD/.logs/.terminal-out.log && cat $WD/.logs/.temp.log >| $WD/.logs/.terminal-out.log && truncate -s 0 $WD/.logs/.temp.log; echo $PWD >> $WD/.logs/.cwd.log; history -a $WD/.logs/.bash_history.log; echo $PWD\$ $(history | tail -n 1) >> $WD/.logs/.history_cwd.log;' 130 | exec > >(tee -ia $WD/.logs/.temp.log) 2>&1 131 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/screen.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import ScreenNav from "./screen-nav"; 3 | import { scramble } from "../tools/utils"; 4 | import { marked } from "marked"; 5 | import Prism from "prismjs"; 6 | import glow from "../tools/glow"; 7 | import { dispatchSubmitTask } from "../node-state"; 8 | import { SocketContext, tasks } from "../tools/socket"; 9 | 10 | const Screen = ({ task = {}, isLightOn }) => { 11 | const socket = useContext(SocketContext); 12 | const [isTask, setIsTask] = useState(Object.keys(task).length > 0); 13 | const [isShowActualScreen, setIsShowActualScreen] = useState(false); 14 | 15 | useEffect(() => { 16 | setIsTask(Object.keys(task).length > 0); 17 | }, [task]); 18 | 19 | async function startTask() { 20 | // Stop flashing 21 | setIsTask(false); 22 | 23 | await animateIntoScreen(); 24 | popActualScreen(); 25 | } 26 | 27 | async function animateIntoScreen() { 28 | const width = window.innerWidth / 130; 29 | const bodyStyle = document.querySelector("body").style; 30 | 31 | const screenBounds = document 32 | .querySelector(".screen") 33 | .getBoundingClientRect(); 34 | const screenCenter = screenBounds.y + screenBounds.height / 2; 35 | bodyStyle.transition = "transform 2s"; 36 | bodyStyle.transformOrigin = `50% ${screenCenter}px`; 37 | bodyStyle.transform = `scale(${width})`; 38 | 39 | await new Promise((resolve) => setTimeout(resolve, 1800)); 40 | } 41 | 42 | async function handleSub(orderNumberSelected) { 43 | dispatchSubmitTask(socket, { task, orderNumberSelected }); 44 | 45 | // Change view back to main screen 46 | const width = window.innerWidth / 130; 47 | const bodyStyle = document.querySelector("body").style; 48 | const screenBounds = document 49 | .querySelector(".screen") 50 | .getBoundingClientRect(); 51 | const screenCenter = screenBounds.y + screenBounds.height / 2; 52 | bodyStyle.transformOrigin = `50% ${screenCenter}px`; 53 | bodyStyle.transform = `scale(${width})`; 54 | await new Promise((resolve) => setTimeout(resolve, 1000)); 55 | 56 | bodyStyle.transition = "transform 2s"; 57 | 58 | bodyStyle.transform = "scale(1)"; 59 | setIsShowActualScreen(false); 60 | } 61 | 62 | function popActualScreen() { 63 | const bodyStyle = document.querySelector("body").style; 64 | bodyStyle.transition = "unset"; 65 | bodyStyle.transform = "scale(1)"; 66 | setIsShowActualScreen(true); 67 | socket.dispatchEvent(tasks({ tasks: [task] })); 68 | } 69 | 70 | return ( 71 | <> 72 |
isTask && startTask()} 74 | style={glow(".screen", isLightOn)} 75 | className={"screen" + (isTask ? " flash" : "")} 76 | disabled={!isTask} 77 | > 78 | {!isTask &&
No tasks to complete
} 79 |
80 | {isShowActualScreen && ( 81 |
82 | 83 | 88 |
89 | )} 90 | 91 | ); 92 | }; 93 | 94 | marked.setOptions({ 95 | highlight: (code, lang) => { 96 | if (Prism.languages[lang]) { 97 | return Prism.highlight(code, Prism.languages[lang], lang); 98 | } else { 99 | return code; 100 | } 101 | }, 102 | }); 103 | 104 | const Quizzer = ({ handleSub, question, options }) => { 105 | const [selected, setSelected] = useState(null); 106 | 107 | function parseMarkdown(markdown = "") { 108 | return marked.parse(markdown, { gfm: true }); 109 | } 110 | 111 | return ( 112 | <> 113 |
114 |
118 |
119 |
    120 | {options.map((option) => { 121 | return ( 122 |
  • 128 | 142 |
  • 143 | ); 144 | })} 145 |
146 |
147 | 157 |
158 | 159 | ); 160 | }; 161 | 162 | export default Screen; 163 | -------------------------------------------------------------------------------- /curriculum/locales/english/build-a-smart-contract-in-rust.md: -------------------------------------------------------------------------------- 1 | # Web3 - Build a Smart Contract in Rust 2 | 3 | ## 1 4 | 5 | ### --description-- 6 | 7 | For this project, you need to create and deploy a Smart Contract using Rust that keeps track of how many users have clicked a button on your web page. 8 | 9 | You are started with Rust library consisting of all the unit and integration tests you are expected to pass. The boilerplate already contains the necessary crates to complete the user stories, but you will need to import them. 10 | 11 | You will be required to define structs and functions matching those used in the tests. You should only need to work in the `build-a-smart-contract-in-rust/src/lib.rs` file. 12 | 13 | **Commands** 14 | 15 | - Run all unit tests: `cargo test --lib` 16 | - Run all integration tests: `wasm-pack test --firefox --headless` 17 | 18 | _Note: You need to run the tests from within the library directory._ 19 | 20 | **User Stories:** 21 | 22 | - Your smart contract uses `wasm-pack` to compile the Rust code to JavaScript for Nodejs 23 | - Your smart contract exports an `initialise` function that returns `Result` 24 | - This function takes no arguments 25 | - This function returns a `Context` wrapped in a `JsValue` 26 | - Your smart contract exports a `set_click` function that returns `Result` 27 | - This function accepts a `JsValue` with the `Context` type 28 | - This function also accepts a `String` as the second argument which is the address of the user who clicked the button 29 | - Your smart contract exports a `get_contract_account` function that returns `Result` 30 | - This function accepts a `JsValue` with the `Context` type 31 | - This function returns an `Account` wrapped in a `JsValue` 32 | - Your library should export a struct named `Account` with the following fields: 33 | - `total_clicks` which is a `u64` 34 | - `clickers` which is a `Vec` 35 | - Your library should export a struct named `Context` with the following field: 36 | - `base_account` which is an `Account` 37 | - You should deploy your smart contract using `node node/deploy.js ` 38 | - You should run `node node/smart-contract.js set_click ` to add at least 3 clickers to the contract 39 | 40 | **Useful Resources:** 41 | 42 | - `https://www.rust-lang.org/what/wasm` - WebAssembly - Rust Programming Language 43 | - `https://rustwasm.github.io/docs/book/` - RustWASM Book 44 | - `https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm` - Compiling from Rust to WebAssembly - WebAssembly | MDN 45 | - `https://rustwasm.github.io/wasm-bindgen/` - wasm-bindgen guide 46 | - `https://rustwasm.github.io/wasm-pack/` - wasm-pack 47 | 48 | **Tips:** 49 | 50 | - You can wrap a value in a `JsValue` using `JsValue::from_serde` 51 | - You can serialize a `JsValue` using the `.into_serde()` method 52 | - Build your smart contract with `nodejs` as the target 53 | 54 | ### --tests-- 55 | 56 | Your smart contract should pass all `lib::tests` unit tests. 57 | 58 | ```js 59 | const { stdout } = await __helpers.getCommandOutput( 60 | 'cargo test --lib tests', 61 | __projectLoc 62 | ); 63 | assert.match(stdout, /test result: ok/); 64 | ``` 65 | 66 | Your smart contract should pass all `initialise` integration tests. 67 | 68 | ```js 69 | const { stdout } = await __helpers.getCommandOutput( 70 | 'wasm-pack test --firefox --headless -- --test initialise', 71 | __projectLoc 72 | ); 73 | assert.match(stdout, /test result: ok/); 74 | ``` 75 | 76 | Your smart contract should pass all `set_click` integration tests. 77 | 78 | ```js 79 | const { stdout } = await __helpers.getCommandOutput( 80 | 'wasm-pack test --firefox --headless -- --test set_click', 81 | __projectLoc 82 | ); 83 | assert.match(stdout, /test result: ok/); 84 | ``` 85 | 86 | Your smart contract should pass all `get_contract_account` integration tests. 87 | 88 | ```js 89 | const { stdout } = await __helpers.getCommandOutput( 90 | 'wasm-pack test --firefox --headless -- --test get_contract_account', 91 | __projectLoc 92 | ); 93 | assert.match(stdout, /test result: ok/); 94 | ``` 95 | 96 | You should deploy your smart contract using `node node/deploy.js `. 97 | 98 | ```js 99 | const blockchain = JSON.parse( 100 | await __helpers.getFile(`${__projectLoc}/node/data/blockchain.json`) 101 | ); 102 | assert.exists(blockchain, 'Could not find blockchain'); 103 | assert.isAtLeast( 104 | blockchain.length, 105 | 2, 106 | 'Expected blockchain to contain at least 2 blocks' 107 | ); 108 | assert.equal(blockchain[1].smartContracts.length, 1); 109 | ``` 110 | 111 | You should add at least 3 different clickers to your contract state. 112 | 113 | ```js 114 | const blockchain = JSON.parse( 115 | await __helpers.getFile(`${__projectLoc}/node/data/blockchain.json`) 116 | ); 117 | const smartContract = blockchain 118 | .reverse() 119 | .find(b => b.smartContracts.length > 0)?.smartContracts?.[0]; 120 | assert.exists(smartContract, 'Smart contract not found'); 121 | assert.isAtLeast( 122 | smartContract.state.base_account.clickers.length, 123 | 3, 124 | 'Not enough clickers' 125 | ); 126 | assert.isAtLeast( 127 | smartContract.state.base_account.total_clicks, 128 | 3, 129 | 'Not enough total_clicks' 130 | ); 131 | ``` 132 | 133 | ### --before-all-- 134 | 135 | ```js 136 | global.__projectLoc = 'build-a-smart-contract-in-rust'; 137 | ``` 138 | 139 | ## --fcc-end-- 140 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/camperbot.css: -------------------------------------------------------------------------------- 1 | .camperbot { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | z-index: 99999; 6 | width: fit-content; 7 | height: 230px; 8 | padding: 10px; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | font-size: 14px; 11 | color: #333; 12 | } 13 | 14 | .camperbot-body { 15 | position: relative; 16 | top: 0; 17 | right: 0; 18 | animation: hover 6s ease-in-out 1s infinite; 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | @keyframes hover { 26 | 0% { 27 | top: 0; 28 | } 29 | 50% { 30 | top: 10px; 31 | } 32 | 100% { 33 | top: 0; 34 | } 35 | } 36 | 37 | .camperbot-hat { 38 | display: flex; 39 | flex-direction: column; 40 | align-items: center; 41 | } 42 | .camperbot-hat:hover { 43 | cursor: pointer; 44 | } 45 | .camperbot-hat > .ball { 46 | border-radius: 50%; 47 | width: 5px; 48 | height: 5px; 49 | background-color: green; 50 | } 51 | .camperbot-hat > .rod { 52 | width: 2px; 53 | height: 25px; 54 | background-color: green; 55 | display: flex; 56 | justify-content: space-evenly; 57 | align-items: center; 58 | flex-direction: column; 59 | } 60 | .camperbot-hat > .rod > .ring { 61 | width: 15px; 62 | height: 1px; 63 | background-color: green; 64 | } 65 | .camperbot-hat > .bowl { 66 | width: 30px; 67 | height: 20px; 68 | border: 1px solid green; 69 | border-top-left-radius: 50%; 70 | border-top-right-radius: 50%; 71 | background-color: green; 72 | } 73 | .camperbot-hat > .bowl > .logo { 74 | background-image: url("https://www.freecodecamp.org/favicon-32x32.png?v=6cba562cbd10e31af925a976f3db73f7"); 75 | width: 105%; 76 | height: 140%; 77 | transform: scale(0.55); 78 | border-top-left-radius: 5px; 79 | border-top-right-radius: 5px; 80 | border-bottom-left-radius: 2px; 81 | border-bottom-right-radius: 2px; 82 | } 83 | 84 | .camperbot-head { 85 | width: 80px; 86 | height: 40px; 87 | background-color: green; 88 | display: grid; 89 | grid-template-columns: repeat(2, 1fr); 90 | grid-template-rows: repeat(2, 1fr); 91 | grid-template-areas: 92 | "eye-left eye-right" 93 | "mouth mouth"; 94 | border-radius: 3px; 95 | } 96 | .camperbot-eye { 97 | border: 1px solid yellow; 98 | width: 10px; 99 | height: 10px; 100 | background-color: yellow; 101 | border-radius: 50%; 102 | margin: auto; 103 | } 104 | .camperbot-eye.left { 105 | grid-area: eye-left; 106 | } 107 | .camperbot-eye.right { 108 | grid-area: eye-right; 109 | } 110 | .camperbot-mouth { 111 | grid-area: mouth; 112 | border: 1px solid yellow; 113 | width: 50%; 114 | height: 9px; 115 | background-color: yellow; 116 | border-radius: 10%; 117 | margin: auto; 118 | } 119 | .camperbot-mouth > .speech-smoke { 120 | width: 0; 121 | height: 0; 122 | border-left: 2vh solid transparent; 123 | border-right: 2vh solid transparent; 124 | border-top: 10vh solid grey; 125 | margin-top: -20px; 126 | margin-left: -30px; 127 | transform: rotate(250deg); 128 | } 129 | .speech-smoke > .speech-bubble { 130 | width: 230px; 131 | min-height: 60px; 132 | background-color: grey; 133 | border-radius: 10%; 134 | padding: 0.4rem; 135 | color: var(--light-2); 136 | transform: rotate(-250deg); 137 | transform-origin: right top; 138 | margin-top: -50px; 139 | margin-left: -220px; 140 | display: flex; 141 | flex-direction: column; 142 | font-size: 1.2rem; 143 | } 144 | .speech-bubble > .speech-buttons { 145 | display: flex; 146 | flex-direction: row; 147 | justify-content: space-evenly; 148 | align-items: center; 149 | } 150 | .speech-buttons > button { 151 | background-color: var(--dark-yellow); 152 | border: 1px solid var(--light-yellow); 153 | border-radius: 5px; 154 | padding: 0.1rem; 155 | width: 55px; 156 | color: var(--light-1); 157 | font-size: 1.2rem; 158 | font-weight: bold; 159 | cursor: pointer; 160 | transition: all 0.2s ease-in-out; 161 | } 162 | .speech-buttons > button:hover { 163 | background-color: var(--light-yellow); 164 | border: 1px solid var(--dark-yellow); 165 | color: var(--dark-1); 166 | } 167 | 168 | .speech-bubble > pre { 169 | white-space: break-spaces; 170 | letter-spacing: 0.15em; /* Adjust as needed */ 171 | animation: blink-caret 0.75s step-end infinite; 172 | } 173 | 174 | @keyframes blink-caret { 175 | from, 176 | to { 177 | border-color: transparent; 178 | } 179 | 50% { 180 | border-color: orange; 181 | } 182 | } 183 | 184 | .camperbot-neck { 185 | width: 10px; 186 | height: 11px; 187 | background-color: green; 188 | } 189 | 190 | .camperbot-torso { 191 | width: 90px; 192 | height: 40px; 193 | text-align: center; 194 | font-weight: 700; 195 | align-items: center; 196 | background-color: green; 197 | display: flex; 198 | justify-content: center; 199 | border-radius: 3px; 200 | } 201 | 202 | /* .camperbot-arm { 203 | } 204 | .camperbot-arm.left { 205 | } 206 | .camperbot-arm.right { 207 | } */ 208 | 209 | /* .camperbot-prop { 210 | } */ 211 | .camperbot-prop > div { 212 | border: 2px solid green; 213 | height: 10px; 214 | border-radius: 40% / 50%; 215 | margin: 10px auto; 216 | animation: propel 4s ease-in-out 0s infinite; 217 | } 218 | .camperbot-prop > .top { 219 | width: 50px; 220 | animation-delay: 0.1s; 221 | } 222 | .camperbot-prop > .mid { 223 | width: 30px; 224 | animation-delay: 0.3s; 225 | } 226 | .camperbot-prop > .bot { 227 | width: 20px; 228 | animation-delay: 0.55s; 229 | } 230 | 231 | @keyframes propel { 232 | 0% { 233 | margin: 10px auto; 234 | } 235 | 50% { 236 | margin: 0px auto; 237 | } 238 | 100% { 239 | margin: 10px auto; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/public/quiz.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "question": "\nWhat's the correct way to display `Hello world`?\n", 4 | "options": [ 5 | { "code": "```js\nconsole.log(\"Hello world\");\n```", "order": 0 }, 6 | { "code": "```py\nprint(\"Hello world\")\n```", "order": 1 }, 7 | { "code": "```c\nprintf(\"Hello world\");\n```", "order": 2 }, 8 | { 9 | "code": "```java\nSystem.out.println(\"Hello world\");\n```", 10 | "order": 3 11 | }, 12 | { "code": "```ruby\nputs \"Hello world\"\n```", "order": 4 }, 13 | { "code": "```php\n\n```", "order": 5 } 14 | ], 15 | "results": { 16 | "correct": [0, 1, 2, 3, 4], 17 | "incorrect": [], 18 | "misbehaved": [5] 19 | } 20 | }, 21 | { 22 | "question": "\nHow do you remove a table named `freeCoedCamp` using SQL?\n", 23 | "options": [ 24 | { "code": "```sql\nREMOVE TABLE freeCodeCamp;\n```", "order": 0 }, 25 | { "code": "```sql\nDROP TABLE freeCodeCamp;\n```", "order": 1 }, 26 | { "code": "```sql\nDESTROY TABLE freeCodeCamp;\n```", "order": 2 }, 27 | { "code": "```sql\n🧨TABLE freeCodeCamp;\n```", "order": 3 }, 28 | { "code": "```sql\nRobert'); DROP TABLE students;--\n```", "order": 4 } 29 | ], 30 | "results": { "correct": [1], "incorrect": [0, 2], "misbehaved": [3, 4] } 31 | }, 32 | { 33 | "question": "\nIn HTML, how do you create an `h2` element that says `CatPhotoApp`?\n", 34 | "options": [ 35 | { "code": "```html\n

CatPhotoApp

\n```", "order": 0 }, 36 | { "code": "```html\nCatPhotoApp\n```", "order": 1 }, 37 | { "code": "```html\nh2 = \"CatPhotoApp\"\n```", "order": 2 }, 38 | { "code": "```html\n

CatPhotoApp

\n```", "order": 3 }, 39 | { 40 | "code": "```text\nWould rather make a dog photo app instead\n```", 41 | "order": 4 42 | } 43 | ], 44 | "results": { "correct": [0], "incorrect": [1, 2, 3], "misbehaved": [4] } 45 | }, 46 | { 47 | "question": "\nWhat is the correct way to create a function in Python?\n", 48 | "options": [ 49 | { "code": "```py\ndef myFunction():\n```", "order": 0 }, 50 | { "code": "```py\nfunction myfunction():\n```", "order": 1 }, 51 | { "code": "```py\ncreate myFunction():\n```", "order": 2 }, 52 | { "code": "```py\nsudo myFunction():\n```", "order": 3 }, 53 | { "code": "```text\nHire a freelancer to do it\n```", "order": 4 } 54 | ], 55 | "results": { "correct": [0], "incorrect": [1, 2, 3], "misbehaved": [4] } 56 | }, 57 | { 58 | "question": "\nWhich symbols are used for a comment in JavaScript?\n", 59 | "options": [ 60 | { "code": "```js\n//\n```", "order": 0 }, 61 | { "code": "```js\n\\\\\n```", "order": 1 }, 62 | { "code": "```js\n/* *\\\n```", "order": 2 }, 63 | { "code": "```js\n\\* *\\\n```", "order": 3 }, 64 | { "code": "```text\n¯\\_(ツ)_/¯\n```", "order": 4 } 65 | ], 66 | "results": { "correct": [0], "incorrect": [1, 2, 3], "misbehaved": [4] } 67 | }, 68 | { 69 | "question": "\nWhat will display on a website with the following HTML/JavaScript?\n\n```html\n

\n\n```\n", 70 | "options": [ 71 | { "code": "```text\n50\n```", "order": 0 }, 72 | { "code": "```text\n10\n```", "order": 1 }, 73 | { "code": "```text\n5\n```", "order": 2 }, 74 | { "code": "```text\nblank\n```", "order": 3 }, 75 | { "code": "```text\nundefined\n```", "order": 4 }, 76 | { 77 | "code": "```text\nI'm too busy learning Web3 to think about this\n```", 78 | "order": 5 79 | } 80 | ], 81 | "results": { "correct": [0], "incorrect": [1, 2, 3, 4], "misbehaved": [5] } 82 | }, 83 | { 84 | "question": "\nWhich does JavaScript interpreter ignore?\n", 85 | "options": [ 86 | { "code": "```text\nSpaces\n```", "order": 0 }, 87 | { "code": "```text\nTabs\n```", "order": 1 }, 88 | { "code": "```text\nNew lines\n```", "order": 2 }, 89 | { "code": "```text\nAll of the above\n```", "order": 3 }, 90 | { "code": "```text\nEvery 3rd semicolon\n```", "order": 4 }, 91 | { "code": "```text\nMost of my code\n```", "order": 5 } 92 | ], 93 | "results": { "correct": [0, 1, 2, 3], "incorrect": [4], "misbehaved": [5] } 94 | }, 95 | { 96 | "question": "\nWhich will print the digits 0 through 9, using Python?\n", 97 | "options": [ 98 | { "code": "```py\nprint(\"0123456789\")\n```", "order": 0 }, 99 | { "code": "```py\nfor i in range(9): print(i)\n```", "order": 1 }, 100 | { 101 | "code": "```py\nfor (let i; i < 10; i++) { console.log(i) }\n```", 102 | "order": 2 103 | }, 104 | { "code": "```py\nfor i in range(10): print(\"i\")\n```", "order": 3 }, 105 | { "code": "```py\nfor i in 0::9: print(i)\n```", "order": 4 } 106 | ], 107 | "results": { "correct": [0], "incorrect": [1, 2, 3, 4], "misbehaved": [] } 108 | }, 109 | { 110 | "question": "\nIf `f(x) = 4x^3 - 4x^2 + 10`, and `f(-2) = a`, what is `a`?\n", 111 | "options": [ 112 | { "code": "```text\n26\n```", "order": 0 }, 113 | { "code": "```text\n-38\n```", "order": 1 }, 114 | { "code": "```text\n10\n```", "order": 2 }, 115 | { "code": "```text\n38\n```", "order": 3 }, 116 | { "code": "```text\nNone of the above\n```", "order": 4 } 117 | ], 118 | "results": { "correct": [0], "incorrect": [1, 2, 3], "misbehaved": [4] } 119 | }, 120 | { 121 | "question": "\nWhat is the output of the following Python code?\n\n```py\nlistOne = [20, 40, 60, 80]\nlistTwo = [20, 40, 60, 80]\nprint(listOne == listTwo, listOne is listTwo)\n```\n", 122 | "options": [ 123 | { "code": "```py\nTrue False\n```", "order": 0 }, 124 | { "code": "```py\nFalse True\n```", "order": 1 }, 125 | { "code": "```py\nTrue True\n```", "order": 2 }, 126 | { "code": "```py\nFalse False\n```", "order": 3 }, 127 | { 128 | "code": "```text\nWhat did True say to False?\nStop boolean me 😆\n```", 129 | "order": 4 130 | } 131 | ], 132 | "results": { "correct": [0], "incorrect": [1, 2, 3], "misbehaved": [4] } 133 | } 134 | ] 135 | -------------------------------------------------------------------------------- /config/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 0, 4 | "title": "Learn Digital Ledgers by Building a Blockchain", 5 | "dashedName": "learn-digital-ledgers-by-building-a-blockchain", 6 | "isIntegrated": false, 7 | "description": "In this course, you will learn how to build a blockchain from scratch using JavaScript.", 8 | "isPublic": true, 9 | "currentLesson": 1, 10 | "runTestsOnWatch": true, 11 | "isResetEnabled": true 12 | }, 13 | { 14 | "id": 1, 15 | "title": "Learn Proof of Work Consensus by Building a Block Mining Algorithm", 16 | "dashedName": "learn-proof-of-work-consensus-by-building-a-block-mining-algorithm", 17 | "isIntegrated": false, 18 | "description": "In this course, you will learn how to use the SHA-256 hashing algorithm make your blockchain more secure.", 19 | "isPublic": true, 20 | "currentLesson": 1, 21 | "runTestsOnWatch": true, 22 | "isResetEnabled": true 23 | }, 24 | { 25 | "id": 2, 26 | "title": "Learn Digital Signatures by Building a Wallet", 27 | "dashedName": "learn-digital-signatures-by-building-a-wallet", 28 | "isIntegrated": false, 29 | "description": "In this course, you will learn how to generate and use keypairs so you can verify users and transactions.", 30 | "isPublic": true, 31 | "currentLesson": 1, 32 | "runTestsOnWatch": true, 33 | "isResetEnabled": true 34 | }, 35 | { 36 | "id": 3, 37 | "title": "Build a Video Game Marketplace Blockchain", 38 | "dashedName": "build-a-video-game-marketplace-blockchain", 39 | "isIntegrated": true, 40 | "description": "In this integrated project, you will build a blockchain where you can buy and sell items for a video game.", 41 | "isPublic": true, 42 | "currentLesson": 1, 43 | "numberOfLessons": 1 44 | }, 45 | { 46 | "id": 4, 47 | "title": "Learn Smart Contracts by Building a Car Loan Contract", 48 | "dashedName": "learn-smart-contracts-by-building-a-car-loan-contract", 49 | "isIntegrated": false, 50 | "description": "In this course, you will learn about immutable digital agreements by building a smart contract for a car loan.", 51 | "isPublic": false, 52 | "currentLesson": 1, 53 | "numberOfLessons": 1 54 | }, 55 | { 56 | "id": 5, 57 | "title": "Learn NFT's by Building a Set of Concert Tickets", 58 | "dashedName": "learn-nfts-by-building-a-set-of-concert-tickets", 59 | "isIntegrated": false, 60 | "description": "In this course, you will learn about digital ownership on the blockchain by building a set of concert tickets.", 61 | "isPublic": false, 62 | "currentLesson": 1, 63 | "numberOfLessons": 1 64 | }, 65 | { 66 | "id": 6, 67 | "title": "Build a Fundraising Smart Contract", 68 | "dashedName": "build-a-fundraising-smart-contract", 69 | "isIntegrated": true, 70 | "description": "In this integrated project, you will create and deploy a smart contract to raise funds for your start-up.", 71 | "isPublic": true, 72 | "currentLesson": 1, 73 | "numberOfLessons": 1 74 | }, 75 | { 76 | "id": 7, 77 | "title": "Learn Websockets by Building a Chat App", 78 | "dashedName": "learn-websockets-by-building-a-chat-app", 79 | "isIntegrated": false, 80 | "description": "To be decided...", 81 | "isPublic": false, 82 | "currentLesson": 1, 83 | "numberOfLessons": 1 84 | }, 85 | { 86 | "id": 8, 87 | "title": "Learn Distributed Networks by Building Distributed Chat App", 88 | "dashedName": "learn-distributed-networks-by-building-a-distributed-chat-app", 89 | "isIntegrated": false, 90 | "description": "To be decided...", 91 | "isPublic": false, 92 | "currentLesson": 1, 93 | "numberOfLessons": 1 94 | }, 95 | { 96 | "id": 9, 97 | "title": "Build a Peer-to-Peer Network", 98 | "dashedName": "build-a-peer-to-peer-network", 99 | "isIntegrated": true, 100 | "description": "In this integrated project, you will build distributed peer-to-peer network for your blockchain.", 101 | "isPublic": true, 102 | "currentLesson": 1, 103 | "numberOfLessons": 1 104 | }, 105 | { 106 | "id": 10, 107 | "title": "Learn Proof of Stake Consensus by Completing a Web3 Game", 108 | "dashedName": "learn-proof-of-stake-consensus-by-completing-a-web3-game", 109 | "isIntegrated": false, 110 | "description": "In this course, you will learn about Proof of Stake Consensus by completing the boilerplate given to you, and playing the Web3 game.", 111 | "isPublic": false, 112 | "currentLesson": 1, 113 | "numberOfLessons": 1 114 | }, 115 | { 116 | "id": 11, 117 | "title": "Learn RPC and IDLs by Building a dApp", 118 | "dashedName": "learn-rpc-and-idls-by-building-a-dapp", 119 | "isIntegrated": false, 120 | "description": "In this course, you will learn about Remote Procedural Calls and Interface Description Languages which are an integral part of most decentralised blockchains.", 121 | "isPublic": false, 122 | "currentLesson": 1, 123 | "numberOfLessons": 1 124 | }, 125 | { 126 | "id": 12, 127 | "title": "Build a Web3 Client-Side Package for Your dApp", 128 | "dashedName": "build-a-web3-client-side-package-for-your-dapp", 129 | "isIntegrated": true, 130 | "description": "In this integrated project, you will put your new Web3 knowledge to the test, by developing a package for use by client-side dApps.", 131 | "isPublic": true, 132 | "currentLesson": 1, 133 | "numberOfLessons": 1 134 | }, 135 | { 136 | "id": 13, 137 | "title": "Learn Basic Rust by Building a CLI Wallet", 138 | "dashedName": "learn-basic-rust-by-building-a-cli-wallet", 139 | "isIntegrated": false, 140 | "description": "Did someone say 'Rust'!?", 141 | "isPublic": false, 142 | "currentLesson": 1, 143 | "numberOfLessons": 1 144 | }, 145 | { 146 | "id": 14, 147 | "title": "Learn Intermediate Rust by Building a Blockchain", 148 | "dashedName": "learn-intermediate-rust-by-building-a-blockchain", 149 | "isIntegrated": false, 150 | "description": "In this course, you will learn Rust by building a blockchain using Rust.", 151 | "isPublic": false, 152 | "currentLesson": 1, 153 | "numberOfLessons": 2 154 | }, 155 | { 156 | "id": 15, 157 | "title": "Learn Smart Contracts by Building a Dumb Contract in Rust", 158 | "dashedName": "learn-smart-contracts-by-building-a-dumb-contract-in-rust", 159 | "isIntegrated": false, 160 | "description": "In this course, you will learn how to use WASM with Rust to compile your code into byte code, store it on your blockchain, and run it.", 161 | "isPublic": false, 162 | "currentLesson": 1, 163 | "numberOfLessons": 1 164 | }, 165 | { 166 | "id": 16, 167 | "title": "Build a Smart Contract in Rust", 168 | "dashedName": "build-a-smart-contract-in-rust", 169 | "isIntegrated": true, 170 | "description": "In this integrated project, you will build a smart contract to store and run on your blockchain.", 171 | "isPublic": true, 172 | "currentLesson": 1, 173 | "numberOfLessons": 1 174 | } 175 | ] -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/client/src/components/main-view.js: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import Server from "./server"; 3 | import Chain from "./chain"; 4 | import Ceiling from "./ceiling"; 5 | import Ground from "./ground"; 6 | import Monitor from "./monitor"; 7 | import Camperbot from "./camperbot"; 8 | import { scramble } from "../tools/utils"; 9 | import { getSelf, NameContext } from "../node-state"; 10 | import { sampleTask } from "../tutorial/state"; 11 | import bubbleJson from "../../public/bubbles.json"; 12 | import { 13 | SocketContext, 14 | tasks as tasksEvent, 15 | chain as chainEvent, 16 | } from "../tools/socket"; 17 | 18 | /** 19 | * The number of tokens added to a single server rack, before overflowing to the next. 20 | */ 21 | const MAX_TOKENS_PER_SERVER = 20; 22 | 23 | const MainView = ({ setIsTutorialing }) => { 24 | const socket = useContext(SocketContext); 25 | const name = useContext(NameContext); 26 | const [tasks, setTasks] = useState([]); 27 | const [chain, setChain] = useState([]); 28 | const [transactionPool, setTransactionPool] = useState([]); 29 | const [nodeAccount, setNodeAccount] = useState(null); 30 | const [serverData, setServerData] = useState([]); 31 | const [isLightOn, setIsLightOn] = useState(true); 32 | const [text, setText] = useState(""); 33 | const [lesson, setLesson] = useState(0); 34 | const [tutorialLessonTest, setTutorialLessonTest] = useState( 35 | () => () => false 36 | ); 37 | 38 | const handleNextBubble = () => { 39 | if (lesson < bubbleJson.length - 1) { 40 | setText(bubbleJson[lesson + 1]?.text ?? ""); 41 | setLesson(lesson + 1); 42 | } 43 | }; 44 | 45 | const handlePreviousBubble = () => { 46 | if (lesson > 0) { 47 | setText(bubbleJson[lesson - 1]?.text ?? ""); 48 | setLesson(lesson - 1); 49 | } 50 | }; 51 | 52 | useEffect(() => { 53 | if (socket) { 54 | socket.addEventListener("tasks", ({ detail: { tasks } }) => { 55 | setTasks(tasks); 56 | }); 57 | socket.addEventListener("chain", ({ detail: { chain } }) => { 58 | const self = getSelf(name, chain); 59 | setChain(chain); 60 | setNodeAccount(() => self); 61 | }); 62 | socket.addEventListener( 63 | "transactionPool", 64 | ({ detail: { transactionPool } }) => { 65 | setTransactionPool(transactionPool); 66 | } 67 | ); 68 | } 69 | }, [socket, name]); 70 | 71 | useEffect(() => { 72 | setText(bubbleJson[lesson]?.text ?? ""); 73 | // eslint-disable-next-line react-hooks/exhaustive-deps 74 | }, [bubbleJson]); 75 | 76 | function toggleLight() { 77 | document.querySelector(".room").classList.toggle("dark"); 78 | setIsLightOn(!isLightOn); 79 | } 80 | 81 | useEffect(() => { 82 | if (nodeAccount) { 83 | const { tokens, staked, racks } = nodeAccount; 84 | let tokensRemaining = tokens; 85 | let stakedRemaining = staked; 86 | let tasksRemaining = tasks.length; 87 | // Create `racks` servers 88 | const servers = []; 89 | for (let i = 0; i < racks; i++) { 90 | const numTokensInServer = 91 | tokensRemaining >= MAX_TOKENS_PER_SERVER 92 | ? MAX_TOKENS_PER_SERVER 93 | : tokensRemaining; 94 | const numStakedInServer = 95 | stakedRemaining >= MAX_TOKENS_PER_SERVER 96 | ? MAX_TOKENS_PER_SERVER 97 | : stakedRemaining; 98 | const numTasksInServer = 99 | tasksRemaining >= MAX_TOKENS_PER_SERVER 100 | ? MAX_TOKENS_PER_SERVER 101 | : tasksRemaining; 102 | const server = { 103 | tasks: numTasksInServer, 104 | tokens: numTokensInServer, 105 | staked: numStakedInServer, 106 | }; 107 | servers.push(server); 108 | tokensRemaining -= numTokensInServer; 109 | stakedRemaining -= numStakedInServer; 110 | tasksRemaining -= numTasksInServer; 111 | } 112 | 113 | setServerData(servers); 114 | } 115 | }, [nodeAccount, tasks]); 116 | 117 | useEffect(() => { 118 | // Test if lesson has been passed: 119 | const testResult = tutorialLessonTest(); 120 | if (testResult) { 121 | setTutorialLessonTest(() => () => false); 122 | handleNextBubble(); 123 | } 124 | // eslint-disable-next-line react-hooks/exhaustive-deps 125 | }, [serverData, nodeAccount, tasks, transactionPool]); 126 | 127 | useEffect(() => { 128 | switch (lesson) { 129 | case 5: 130 | setTutorialLessonTest(() => () => { 131 | // Test if enough tokens have been staked 132 | return nodeAccount.staked >= 91; 133 | }); 134 | break; 135 | case 6: 136 | // Set task 137 | socket.dispatchEvent(tasksEvent({ tasks: [sampleTask] })); 138 | break; 139 | case 10: 140 | setTutorialLessonTest(() => () => { 141 | // Test screen is clicked on 142 | return document.querySelector(".actual-screen"); 143 | }); 144 | break; 145 | case 11: 146 | setTutorialLessonTest(() => () => { 147 | // Test task is submitted 148 | const taskLength = tasks.length; 149 | return taskLength; 150 | }); 151 | break; 152 | case 14: 153 | // Task has been validated 154 | const tutorialChain = [ 155 | { 156 | ...chain[0], 157 | data: [ 158 | { 159 | ...chain[0].data[0], 160 | tokens: chain[0].data[0].tokens + 1, 161 | reputation: chain[0].data[0].reputation + 1, 162 | }, 163 | ], 164 | }, 165 | ]; 166 | socket.dispatchEvent(chainEvent({ chain: tutorialChain })); 167 | break; 168 | case 17: 169 | setTutorialLessonTest(() => () => { 170 | // Test rack is bought 171 | return nodeAccount.racks >= 9; 172 | }); 173 | break; 174 | case 20: 175 | // Hacked 176 | hacked(); 177 | // Default starting state 178 | setIsTutorialing(false); 179 | break; 180 | default: 181 | break; 182 | } 183 | function hacked() { 184 | let i = 0; 185 | const interval = setInterval(() => { 186 | if (i >= 10) { 187 | if (!isLightOn) { 188 | toggleLight(); 189 | } 190 | return clearInterval(interval); 191 | } 192 | toggleLight(); 193 | i++; 194 | }, 200); 195 | } 196 | // eslint-disable-next-line react-hooks/exhaustive-deps 197 | }, [lesson]); 198 | 199 | return ( 200 |
201 | 202 | 209 | 210 |
211 |
212 | 213 |
214 | {serverData.map((server, i) => ( 215 | 216 | ))} 217 |
218 |
219 |
220 | 221 |
222 | ); 223 | }; 224 | 225 | export default MainView; 226 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/README.md: -------------------------------------------------------------------------------- 1 | # freeCodeCamp - Proof of Stake Prototype 2 | 3 | Prototype Proof of Stake application for Web3 curriculum 4 | 5 | ## How to Use? 6 | 7 | 1. Build client 8 | 9 | ```bash 10 | npm run build:client 11 | ``` 12 | 13 | 2. Build the blockchain 14 | 15 | ```bash 16 | npm run build:blockchain 17 | ``` 18 | 19 | 3. Start node server with a name 20 | 21 | ```bash 22 | NAME=Camper npm run start:node 23 | ``` 24 | 25 | 4. This will dynamically open a port, which is shown in the same terminal. Open the shown port. 26 | 27 | ### Prerequisites 28 | 29 | - Intermediate JavaScript and Node.js 30 | - Basic HTML and CSS 31 | 32 | - Cryptography 33 | - Hashing Functions 34 | - Signing 35 | - Verifying 36 | - Programatically Solving Hashes 37 | - Blockchains 38 | - Linked Lists 39 | - Blocks 40 | - Tokens 41 | - Wallets 42 | - Addresses 43 | - Private Keys 44 | - Public Keys 45 | - Smart Contracts 46 | - Creation 47 | - Testing 48 | - Deployment 49 | 50 | ### Learning Outcomes 51 | 52 | - Validating 53 | - Staking 54 | - Security 55 | - 51% Attacks 56 | - Rust 57 | 58 | ## Project Ideas 59 | 60 | - Accumulation of resources increases probability of being chosen to do work 61 | - If chosen to do work, you have the option of doing what is requested, or _misbehaving_ 62 | - Completing work gets you a reward 63 | - _Misbehaving_ might get you more of a reward 64 | - _Misbehaving_ might get you a punishment 65 | - Punishment is reduction of resources 66 | - Whether you get punished or not is dependent on the number of _players_ watching you 67 | - If you own `>50%` of the resources, you can _misbehave_ without punishment, provided there is only _one_ player watching you 68 | 69 | Intern at freeCodeCamp in our server room. Help debug this VM. Improve it, or introduce more bugs. If you introduce **any** jQuery, you have _definitely_ misbehaved 70 | 71 | - Resources 72 | - Money to purchase skillsets. Buy Nodejs Course... Buy jQuery course to misbehave 73 | - Reputation - trust - watched less closely by nodes. Higher rep === more work 74 | - Tasks 75 | - Quiz 76 | 77 | --- 78 | 79 | ## Proof of Stake 80 | 81 | ### Glossary 82 | 83 | **Node** - An instance of a server running the blockchain protocol 84 | **Client** - A browser that connects to the blockchain 85 | **Validator** - A node ensuring validity of the blockchain 86 | **Miner** - A node chosen to mine the next block 87 | 88 | ### Hamilton Basic Protocol (HBP) 89 | 90 | The _miner_ forges the next _miner_, upon validation. 91 | 92 | The _miner_ is determined by weight of reputation and staking. 93 | 94 | A _validator_ is determined by weight of reputation. 95 | 96 | The number of _validators_ is determined by some weight of the _miner's_ reputation. 97 | 98 | At least two _nodes_ are requested to validate a _view_ of the blockchain by a _client_ 99 | 100 | **Specification** 101 | 102 | 1. Genesis block is forged by initial _node_ 103 | 2. Block predetermines the _miner_ and _validator(s)_ 104 | 3. Next _validator(s)_ are responsible for distributing reward 105 | 4. Last _validator(s)_ are responsible for distributing blockchain to _client_ 106 | 5. Pool is sorted by _weight_ 107 | 6. Reward is `+1` token, and `+1` reputation 108 | 7. Incorrect block yields no reward 109 | 8. Misbehaved block yields punishment of `-1` token, and `-1` reputation 110 | 111 | 112 | #### Algorithms 113 | 114 | **Terms** 115 | 116 | $$ 117 | n_i^t = \text{number of tokens of }i^{th} \text{ node}\\ 118 | n_i^r = \text{number of reputation of }i^{th} \text{ node}\\ 119 | n_i^s = \text{number of staked tokens of }i^{th} \text{ node}\\ 120 | n_i^i = \text{node index in sorted array}\\ 121 | \;\\ 122 | N_n = \text{total number of nodes}\\ 123 | N_t = \text{total number of tokens}\\ 124 | N_r = \text{total number of reputation}\\ 125 | \;\\ 126 | w_i = \text{weight of }i^{th} \text{ node}\\ 127 | W_n = \text{total weight of nodes}\\ 128 | $$ 129 | 130 | **Weight** 131 | 132 | $$ 133 | w_i = n_i^s + n_i^r\\ 134 | W_n = \sum_{i=0}^{N_n - 1} w_i 135 | $$ 136 | 137 | **Validator** 138 | 139 | $$ 140 | \text{cumulative } v_i = \sum_{x=0}^{i} \frac{n_x^r}{N_r}\\ 141 | $$ 142 | 143 | ``` 144 | // List of elements sorted by weight 145 | [ele1, ele2, ele3] 146 | 147 | // Example list 148 | => [0.66, 0.16, 0.16] 149 | 150 | // Cumulative weight summation 151 | => c_v = [0.66, 0.82, 1.0] 152 | 153 | random_number = generate_random_number(0, 1) 154 | for ele, index in enumerate(c_v): 155 | if random_number < ele: 156 | return index // Index of element interested in 157 | ``` 158 | 159 | **Miner** 160 | 161 | $$ 162 | i = \frac{w_i}{W_n}\\ 163 | $$ 164 | 165 | ### Chain 166 | 167 | ```json 168 | ["Block"] 169 | ``` 170 | 171 | ### Block 172 | 173 | Genesis block will contain all data for initial nodes. 174 | A Node joining the network will create a new block. 175 | 176 | ```json 177 | { 178 | "id": 0, 179 | "hash": "0x0", 180 | "previous_hash": "genesis", 181 | "timestamp": 1496314658000, 182 | "data": [ 183 | { 184 | "name": "Camper", 185 | "staked": 0, 186 | "tokens": 1, 187 | "reputation": 1 188 | } 189 | ], 190 | "nonce": 0, 191 | "next_miner": "Camper", 192 | "next_validators": ["Tom", "Mrugesh", "Quincy"] 193 | } 194 | ``` 195 | 196 | ### Node 197 | 198 | ```json 199 | { 200 | "name": "Camper", 201 | "peer_id": "0x1234567890123456789012345678901234567890", 202 | "staked": 0, 203 | "tokens": 1, 204 | "reputation": 1 205 | } 206 | ``` 207 | 208 | ### Validator 209 | 210 | Same as Node 211 | 212 | ## Data Handling 213 | 214 | Client request streams are not necessarily persisted, as mining could take _too long_. Instead, response from network is always just result of connection. 215 | 216 | Always pass `chain` to and from node/blockchain. 217 | 218 | ### Network Structure 219 | 220 | - `node_1` starts 221 | - Tries to connect to `peers` 222 | - Fails, because no other peers are available 223 | - Listens for connections from `clients` 224 | - `node_2` starts 225 | - Tries to connect to `peers` 226 | - Succeeds, because `node_1` is available 227 | - Listens for connections from `clients` 228 | 229 | 1. Initialise `chain` 230 | 2. Wait for at least 3 nodes on the network 231 | 3. Intial `node` mines genesis block (pass in `chain`, get `chain` back) 232 | 4. 233 | 234 | ## Security 235 | 236 | ### Known Holes and Potential Patches 237 | 238 | **Hole**: Currently, any node can alter the blockchain code, and still mine a valid block. 239 | **Patch**: Blockchain (Rust code) should be compiled with a checksum, and if the checksum fails, the block is invalid. 240 | 241 | **Hole**: If low rep validator is chosen, perhaps its validations should be validated. 242 | 243 | ## Notes 244 | 245 | - Reputation is chance-based 246 | - Story: 25% chance to earn rep on first, 50% chance on second 247 | - Rep still determines how many server-racks 248 | - Maybe buy rack with tokens - spend tokens 249 | - unlock ability to buy rack, by gaining rep 250 | 251 | - Camper starts with `10 tokens`, `0 rep` 252 | - Must spend `x tokens` to buy `rep` (rack) 253 | 254 | Camper is getting hacked. You notice just in time to save 1 rack (10 tokens). 255 | 256 | 1. `n1` initialises chain (no validation) 257 | 2. `n1` listens for connections 258 | 3. `n2` connects to `n1` 259 | 4. `n1` sends chain to `n2` (no validation) 260 | 5. `n2` adds self to chain 261 | 6. `n2` broadcasts chain for validation 262 | 7. `n1` validates chain 263 | 8. `n1` broadcasts chain as valid 264 | 265 | ### Single Node Online 266 | 267 | 1. Genesis block is mined 268 | 2. Task is assigned 269 | 3. Task is submitted 270 | 4. Task is validated 271 | 5. Validation result is mined with data 272 | 6. Block is validated 273 | 274 | ### Multiple Nodes Online 275 | 276 | 1. `n1` mines genesis block 277 | 2. `n1` gets task 278 | 3. `n2` gets chain from `n1` 279 | 4. `n1` submits task 280 | 5. `n2` validates task 281 | 6. `n1` mines with validation result 282 | 7. `n2` validates and broadcasts chain 283 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/assets/quiz.md: -------------------------------------------------------------------------------- 1 | # freeCodeCamp - Quiz Questions 2 | 3 | 4 | 5 | ## 1 6 | 7 | ### Question 8 | 9 | What's the correct way to display `Hello world`? 10 | 11 | ### Options 12 | 13 | ```js 14 | console.log("Hello world"); 15 | ``` 16 | 17 | ```py 18 | print("Hello world") 19 | ``` 20 | 21 | ```c 22 | printf("Hello world"); 23 | ``` 24 | 25 | ```java 26 | System.out.println("Hello world"); 27 | ``` 28 | 29 | ```ruby 30 | puts "Hello world" 31 | ``` 32 | 33 | ```php 34 | 35 | ``` 36 | 37 | ### Results 38 | 39 | ```yml 40 | correct: [0, 1, 2, 3, 4] 41 | incorrect: [] 42 | misbehaved: [5] 43 | ``` 44 | 45 | ## 2 46 | 47 | ### Question 48 | 49 | How do you remove a table named `freeCodeCamp` using SQL? 50 | 51 | ### Options 52 | 53 | ```sql 54 | REMOVE TABLE freeCodeCamp; 55 | ``` 56 | 57 | ```sql 58 | DROP TABLE freeCodeCamp; 59 | ``` 60 | 61 | ```sql 62 | DESTROY TABLE freeCodeCamp; 63 | ``` 64 | 65 | ```sql 66 | 🧨TABLE freeCodeCamp; 67 | ``` 68 | 69 | ```sql 70 | Robert'); DROP TABLE students;-- 71 | ``` 72 | 73 | ### Results 74 | 75 | ```yml 76 | correct: [1] 77 | incorrect: [0, 2] 78 | misbehaved: [3, 4] 79 | ``` 80 | 81 | ## 3 82 | 83 | ### Question 84 | 85 | In HTML, how do you create an `h2` element that says `CatPhotoApp`? 86 | 87 | ### Options 88 | 89 | ```html 90 |

CatPhotoApp

91 | ``` 92 | 93 | ```html 94 | CatPhotoApp 95 | ``` 96 | 97 | ```html 98 | h2 = "CatPhotoApp" 99 | ``` 100 | 101 | 102 | ```html 103 |

CatPhotoApp

104 | ``` 105 | 106 | 107 | ```text 108 | Would rather make a dog photo app instead 109 | ``` 110 | 111 | ### Results 112 | 113 | ```yml 114 | correct: [0] 115 | incorrect: [1, 2, 3] 116 | misbehaved: [4] 117 | ``` 118 | 119 | ## 4 120 | 121 | ### Question 122 | 123 | What is the correct way to create a function in Python? 124 | 125 | ### Options 126 | 127 | ```py 128 | def myFunction(): 129 | ``` 130 | 131 | ```py 132 | function myfunction(): 133 | ``` 134 | 135 | ```py 136 | create myFunction(): 137 | ``` 138 | 139 | ```py 140 | sudo myFunction(): 141 | ``` 142 | 143 | ```text 144 | Hire a freelancer to do it 145 | ``` 146 | 147 | ### Results 148 | 149 | ```yml 150 | correct: [0] 151 | incorrect: [1, 2, 3] 152 | misbehaved: [4] 153 | ``` 154 | 155 | ## 5 156 | 157 | ### Question 158 | 159 | Which symbols are used for a comment in JavaScript? 160 | 161 | ### Options 162 | 163 | ```js 164 | // 165 | ``` 166 | 167 | ```js 168 | \\ 169 | ``` 170 | 171 | ```js 172 | /* *\ 173 | ``` 174 | 175 | ```js 176 | \* *\ 177 | ``` 178 | 179 | ```text 180 | ¯\_(ツ)_/¯ 181 | ``` 182 | 183 | ### Results 184 | 185 | ```yml 186 | correct: [0] 187 | incorrect: [1, 2, 3] 188 | misbehaved: [4] 189 | ``` 190 | 191 | ## 6 192 | 193 | ### Question 194 | 195 | What will display on a website with the following HTML/JavaScript? 196 | 197 | ```html 198 |

199 | 204 | ``` 205 | 206 | ### Options 207 | 208 | ```text 209 | 50 210 | ``` 211 | 212 | ```text 213 | 10 214 | ``` 215 | 216 | ```text 217 | 5 218 | ``` 219 | 220 | ```text 221 | blank 222 | ``` 223 | 224 | ```text 225 | undefined 226 | ``` 227 | 228 | ```text 229 | I'm too busy learning Web3 to think about this 230 | ``` 231 | 232 | ### Results 233 | 234 | ```yml 235 | correct: [0] 236 | incorrect: [1, 2, 3, 4] 237 | misbehaved: [5] 238 | ``` 239 | 240 | ## 7 241 | 242 | ### Question 243 | 244 | Which does JavaScript interpreter ignore? 245 | 246 | ### Options 247 | 248 | ```text 249 | Spaces 250 | ``` 251 | 252 | ```text 253 | Tabs 254 | ``` 255 | 256 | ```text 257 | New lines 258 | ``` 259 | 260 | ```text 261 | All of the above 262 | ``` 263 | 264 | ```text 265 | Every 3rd semicolon 266 | ``` 267 | 268 | ```text 269 | Most of my code 270 | ``` 271 | 272 | ### Results 273 | 274 | ```yml 275 | correct: [0, 1, 2, 3] 276 | incorrect: [4] 277 | misbehaved: [5] 278 | ``` 279 | 280 | ## 8 281 | 282 | ### Question 283 | 284 | Which will print the digits 0 through 9, using Python? 285 | 286 | ### Options 287 | 288 | ```py 289 | print("0123456789") 290 | ``` 291 | 292 | ```py 293 | for i in range(9): print(i) 294 | ``` 295 | 296 | ```py 297 | for (let i; i < 10; i++) { console.log(i) } 298 | ``` 299 | 300 | ```py 301 | for i in range(10): print("i") 302 | ``` 303 | 304 | ```py 305 | for i in 0::9: print(i) 306 | ``` 307 | 308 | ### Results 309 | 310 | ```yml 311 | correct: [0] 312 | incorrect: [1, 2, 3, 4] 313 | misbehaved: [] 314 | ``` 315 | 316 | ## 9 317 | 318 | ### Question 319 | 320 | If `f(x) = 4x^3 - 4x^2 + 10`, and `f(-2) = a`, what is `a`? 321 | 322 | ### Options 323 | 324 | ```text 325 | 26 326 | ``` 327 | 328 | ```text 329 | -38 330 | ``` 331 | 332 | ```text 333 | 10 334 | ``` 335 | 336 | ```text 337 | 38 338 | ``` 339 | 340 | ```text 341 | None of the above 342 | ``` 343 | 344 | ### Results 345 | 346 | ```yml 347 | correct: [0] 348 | incorrect: [1, 2, 3] 349 | misbehaved: [4] 350 | ``` 351 | 352 | ## 10 353 | 354 | ### Question 355 | 356 | What is the output of the following Python code? 357 | 358 | ```py 359 | listOne = [20, 40, 60, 80] 360 | listTwo = [20, 40, 60, 80] 361 | print(listOne == listTwo, listOne is listTwo) 362 | ``` 363 | 364 | ### Options 365 | 366 | ```py 367 | True False 368 | ``` 369 | 370 | ```py 371 | False True 372 | ``` 373 | 374 | ```py 375 | True True 376 | ``` 377 | 378 | ```py 379 | False False 380 | ``` 381 | 382 | ```text 383 | What did True say to False? 384 | Stop boolean me 😆 385 | ``` 386 | 387 | ### Results 388 | 389 | ```yml 390 | correct: [0] 391 | incorrect: [1, 2, 3] 392 | misbehaved: [4] 393 | ``` 394 | 395 | ## 11 396 | 397 | ### Question 398 | 399 | Which is **not** a way to display `Hello world`, using JavaScript? 400 | 401 | ### Options 402 | 403 | ```js 404 | console.log("Hello world"); 405 | ``` 406 | 407 | ```js 408 | document.write("Hello world"); 409 | ``` 410 | 411 | ```js 412 | window.alert("Hello world"); 413 | ``` 414 | 415 | ```text 416 | None of the above 417 | ``` 418 | 419 | ### Results 420 | 421 | ```yml 422 | correct: [3] 423 | incorrect: [] 424 | misbehaved: [0, 1, 2] 425 | ``` 426 | 427 | ## 12 428 | 429 | ### Question 430 | 431 | ### Options 432 | 433 | ### Results 434 | 435 | ## 13 436 | 437 | ### Question 438 | 439 | ### Options 440 | 441 | ### Results 442 | 443 | ## 14 444 | 445 | ### Question 446 | 447 | ### Options 448 | 449 | ### Results 450 | 451 | ## 15 452 | 453 | ### Question 454 | 455 | ### Options 456 | 457 | ### Results 458 | 459 | ## 16 460 | 461 | ### Question 462 | 463 | ### Options 464 | 465 | ### Results 466 | 467 | ## 17 468 | 469 | ### Question 470 | 471 | ### Options 472 | 473 | ### Results 474 | 475 | ## 18 476 | 477 | ### Question 478 | 479 | ### Options 480 | 481 | ### Results 482 | 483 | ## 19 484 | 485 | ### Question 486 | 487 | ### Options 488 | 489 | ### Results 490 | 491 | ## 20 492 | 493 | ### Question 494 | 495 | ### Options 496 | 497 | ### Results 498 | 499 | ## 21 500 | 501 | ### Question 502 | 503 | ### Options 504 | 505 | ### Results 506 | 507 | ## 22 508 | 509 | ### Question 510 | 511 | ### Options 512 | 513 | ### Results 514 | 515 | ## 23 516 | 517 | ### Question 518 | 519 | ### Options 520 | 521 | ### Results 522 | 523 | ## 24 524 | 525 | ### Question 526 | 527 | ### Options 528 | 529 | ### Results 530 | 531 | ## 25 532 | 533 | ### Question 534 | 535 | ### Options 536 | 537 | ### Results 538 | 539 | ## 26 540 | 541 | ### Question 542 | 543 | ### Options 544 | 545 | ### Results 546 | 547 | ## 27 548 | 549 | ### Question 550 | 551 | ### Options 552 | 553 | ### Results 554 | 555 | ## 28 556 | 557 | ### Question 558 | 559 | ### Options 560 | 561 | ### Results 562 | 563 | ## 29 564 | 565 | ### Question 566 | 567 | ### Options 568 | 569 | ### Results 570 | 571 | ## 30 572 | 573 | ### Question 574 | 575 | ### Options 576 | 577 | ### Results 578 | 579 | ## 31 580 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/node/events/node-events.js: -------------------------------------------------------------------------------- 1 | import { nodeState } from "../state.js"; 2 | import { 3 | handle_mine, 4 | handle_validate, 5 | } from "../../blockchain/pkg/blockchain.js"; 6 | import { parse } from "../../utils/websockets/index.js"; 7 | import { debug } from "../../utils/logger.js"; 8 | 9 | /** 10 | * Emitted when a node finds a peer already on the network 11 | * If receiving node is the next miner, an `UpdateChain` transaction is added to the transaction pool 12 | */ 13 | export function connect(data, name) { 14 | if (nodeState.isNextMiner()) { 15 | nodeState.transactionPool.push({ 16 | event: "UpdateChain", 17 | name, 18 | }); 19 | } 20 | } 21 | 22 | /** 23 | * Emitted when a node updates the blockchain 24 | * - If the updated chain is longer than the current chain, the node state chain is updated 25 | * - Adds an `UpdateChain` transaction to the transaction pool 26 | * - Adds the sender node to the network state 27 | * - Broadcasts an `update-chain` event to all connected clients 28 | */ 29 | export function updateChain(data, name) { 30 | if (nodeState.chain.length < data.chain.length) { 31 | nodeState.chain = data.chain; 32 | } 33 | nodeState.transactionPool.push({ 34 | event: "UpdateChain", 35 | name, 36 | }); 37 | nodeState.network.add(name); 38 | nodeState.clientSocks.forEach((sock) => { 39 | sock.send( 40 | parse({ 41 | data: { 42 | chain: nodeState.chain, 43 | tasks: nodeState.tasks, 44 | transactionPool: nodeState.transactionPool, 45 | }, 46 | name, 47 | type: "update-chain", 48 | }) 49 | ); 50 | }); 51 | } 52 | 53 | /** 54 | * Emitted when a client node buys a rack 55 | * - If the receiver node is the next miner, a `BuyRack` transaction is added to the transaction pool 56 | */ 57 | export function buyRack(data, name) { 58 | if (nodeState.isNextMiner()) { 59 | // Add transaction to pool 60 | nodeState.transactionPool.push({ 61 | event: "BuyRack", 62 | name, 63 | }); 64 | } 65 | } 66 | 67 | /** 68 | * Emitted when a client node stakes a token 69 | * - If the receiver node is the next miner, a `Stake` transaction is added to the transaction pool 70 | */ 71 | export function stake(data, name) { 72 | if (nodeState.isNextMiner()) { 73 | // Add transaction to pool 74 | nodeState.transactionPool.push({ 75 | event: "Stake", 76 | name, 77 | }); 78 | } 79 | } 80 | 81 | /** 82 | * Emitted when a client node unstakes a token 83 | * - If the receiver node is the next miner, a `Unstake` transaction is added to the transaction pool 84 | */ 85 | export function unstake(data, name) { 86 | if (nodeState.isNextMiner()) { 87 | // Add transaction to pool 88 | nodeState.transactionPool.push({ 89 | event: "Unstake", 90 | name, 91 | }); 92 | } 93 | } 94 | 95 | /** 96 | * Emitted when a client node submits a task 97 | * - If the receiver node is a next validator: 98 | * - The task is validated 99 | * - A `SubmitTask` transaction is added to the transaction pool 100 | * - An `update-chain` event is broadcast to all connected clients 101 | * - A `task-validated` event is broadcast to all peer nodes 102 | */ 103 | export function submitTask(data, name) { 104 | if (nodeState.isNextValidator()) { 105 | debug("Validating: ", data, name); 106 | const { taskValid } = handleSubmitTask({ 107 | data, 108 | name, 109 | }); 110 | nodeState.transactionPool.push({ 111 | name, 112 | event: "SubmitTask", 113 | }); 114 | nodeState.clientSocks.forEach((sock) => { 115 | sock.send( 116 | parse({ 117 | data: { 118 | chain: nodeState.chain, 119 | tasks: nodeState.tasks, 120 | transactionPool: nodeState.transactionPool, 121 | }, 122 | name, 123 | type: "update-chain", 124 | }) 125 | ); 126 | }); 127 | broadcast({ 128 | data: { taskValid }, 129 | name, 130 | type: "task-validated", 131 | }); 132 | } 133 | } 134 | 135 | /** 136 | * Emitted when a node mines a block 137 | * - If the receiver node is a next validator: 138 | * - The block is validated 139 | * - If the block is valid, the block is added to the chain, broadcast to all connected clients, and a `block-validated` event is broadcast 140 | * - If the block is invalid, a `block-invalidated` event is broadcast 141 | */ 142 | export function blockMined(data, name) { 143 | // If isNextValidator, then validate, and emit "block-validated" 144 | if (nodeState.isNextValidator()) { 145 | const isValid = handle_validate({ 146 | chain: data.chain, 147 | network: Array.from(nodeState.network), 148 | }); 149 | if (isValid) { 150 | nodeState.chain = data.chain; 151 | nodeState.clientSocks.forEach((sock) => { 152 | sock.send( 153 | parse({ 154 | data: { 155 | chain: nodeState.chain, 156 | tasks: nodeState.tasks, 157 | transactionPool: nodeState.transactionPool, 158 | }, 159 | name, 160 | type: "update-chain", 161 | }) 162 | ); 163 | }); 164 | broadcast({ data, name, type: "block-validated" }); 165 | } else { 166 | broadcast({ data, name, type: "block-invalidated" }); 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * Emitted when a validator node invalidates a proposed block 173 | * - If the receiver node is the next miner, a `BlockInvalidated` transaction is added to the transaction pool 174 | * - A new block is mined and proposed with a punishment for the bad actor 175 | * - The transaction pool is cleared 176 | */ 177 | export function blockInvalidated(data, name) { 178 | // If next miner, add punishment to transaction pool 179 | if (nodeState.isNextMiner()) { 180 | nodeState.transactionPool.push({ 181 | event: "BlockInvalidated", 182 | name, 183 | }); 184 | // Try mine again 185 | const [{ chain: proposedChain }, errors] = handle_mine({ 186 | chain: { 187 | chain: nodeState.chain, 188 | network: Array.from(nodeState.network), 189 | }, 190 | transactions: nodeState.transactionPool, 191 | task_valid: false, // Mine again, but with punishment 192 | }); 193 | debug("Proposed chain: ", proposedChain, errors); 194 | // Clear transaction pool 195 | nodeState.transactionPool = []; 196 | // Handle proposed chain 197 | handleProposedBlock(proposedChain); 198 | } 199 | } 200 | 201 | /** 202 | * Emitted when a validator node validates a proposed block 203 | * - If receiving node is the next miner, a random task is added to the node state 204 | * - An `update-chain` event is broadcast to all connected clients 205 | */ 206 | export function blockValidated(data, name) { 207 | // Emitted event from next_validators. Contains most up-to-date chain. 208 | nodeState.chain = data.chain; 209 | 210 | if (nodeState.isNextMiner()) { 211 | addTaskToState(getRandomTask()); 212 | } 213 | // Send client updated chain 214 | nodeState.clientSocks.forEach((sock) => { 215 | sock.send( 216 | parse({ 217 | data: { 218 | chain: nodeState.chain, 219 | tasks: nodeState.tasks, 220 | transactionPool: nodeState.transactionPool, 221 | }, 222 | name, 223 | type: "update-chain", 224 | }) 225 | ); 226 | }); 227 | } 228 | 229 | /** 230 | * Emitted when a validator has validated a task 231 | * - A new block is mined, and the resulting chain is proposed 232 | * - The transaction pool is cleared 233 | */ 234 | export function taskValidated(data, name) { 235 | const [{ chain: proposedChain }, errors] = handle_mine({ 236 | chain: { chain: nodeState.chain, network: Array.from(nodeState.network) }, 237 | transactions: nodeState.transactionPool, 238 | task_valid: data.taskValid, 239 | }); 240 | debug("Proposed chain: ", proposedChain, errors); 241 | // Clear transaction pool 242 | nodeState.transactionPool = []; 243 | // Handle proposed chain 244 | handleProposedBlock(proposedChain); 245 | } 246 | -------------------------------------------------------------------------------- /learn-proof-of-stake-consensus-by-completing-a-web3-game/blockchain/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Blockchain 2 | //! 3 | //! `blockchain` is a WASM module for handling a Proof of Stake blockchain. 4 | 5 | pub mod block; 6 | pub mod chain; 7 | pub mod node; 8 | 9 | use block::Block; 10 | use chain::Chain; 11 | use node::Node; 12 | 13 | use rand::Rng; 14 | use serde::{Deserialize, Serialize}; 15 | use sha2::{Digest, Sha256}; 16 | use wasm_bindgen::prelude::*; 17 | 18 | /// Increasing the number of leading zeros in the hash of a block increases the difficulty of mining a block. 19 | pub static DIFFICULTY_PREFIX: &str = "0"; 20 | 21 | /// Events that can be emitted in the `event` field of a `Transaction`. 22 | #[derive(Serialize, Deserialize, Debug, Clone)] 23 | pub enum Events { 24 | BuyRack, 25 | BlockInvalidated, 26 | Stake, 27 | SubmitTask, 28 | Unstake, 29 | UpdateChain, 30 | } 31 | 32 | /// A transaction describes the change which needs to be mined into a block. The transaction is associated with the `name` of a Node. 33 | #[derive(Serialize, Deserialize, Debug, Clone)] 34 | pub struct Transaction { 35 | pub event: Events, 36 | pub name: String, 37 | } 38 | 39 | /// The current state of the Node calling the API. 40 | #[derive(Serialize, Deserialize, Debug, Clone)] 41 | pub struct NodeState { 42 | pub chain: Chain, 43 | pub transactions: Vec, 44 | pub task_valid: bool, 45 | } 46 | 47 | /// The structure of the response from the Blockchain API. 48 | #[derive(Serialize, Deserialize, Debug)] 49 | pub struct Res { 50 | pub chain: Chain, 51 | pub errors: Vec, 52 | } 53 | 54 | /// Mines the next block onto the given chain passed in the `node_state` argument. 55 | /// 56 | /// # Examples 57 | /// 58 | /// ```js 59 | /// const nodeState = { 60 | /// chain: { 61 | /// chain: [], 62 | /// network: ["node_1", "node_2"], 63 | /// }, 64 | /// transactions: [ 65 | /// { 66 | /// event: "UpdateChain", 67 | /// name: "node_2" 68 | /// } 69 | /// ], 70 | /// task_valid: true 71 | /// }; 72 | /// const result = handle_mine(nodeState); 73 | /// ``` 74 | #[wasm_bindgen] 75 | pub fn handle_mine(node_state: JsValue) -> Result { 76 | let node_state: NodeState = node_state.into_serde()?; 77 | let mut chain = node_state.chain; 78 | let mut errors: Vec = vec![]; 79 | 80 | let mut unique_nodes: Vec = vec![]; 81 | for transaction in node_state.transactions.iter() { 82 | if let Some(node) = chain.get_node_by_name(&transaction.name) { 83 | if !unique_nodes.iter().any(|n| n.name == node.name) { 84 | let node = node.clone(); 85 | unique_nodes.push(node); 86 | } 87 | } 88 | } 89 | 90 | for transaction in node_state.transactions.iter() { 91 | if let Some(node) = unique_nodes.iter_mut().find(|n| n.name == transaction.name) { 92 | match transaction.event { 93 | Events::Unstake => { 94 | if node.can_unstake() { 95 | node.staked -= 1; 96 | } else { 97 | errors.push(format!("{} cannot unstake", node.name)); 98 | } 99 | } 100 | Events::Stake => { 101 | if node.can_stake() { 102 | node.staked += 1; 103 | } else { 104 | errors.push(format!("{} cannot stake", node.name)); 105 | } 106 | } 107 | Events::BuyRack => { 108 | if node.can_buy_rack() { 109 | node.racks += 1; 110 | node.tokens -= 10; 111 | } else { 112 | errors.push(format!("{} cannot buy rack", node.name)); 113 | } 114 | } 115 | Events::BlockInvalidated => { 116 | if node.can_punish() { 117 | if node.tokens == node.staked { 118 | node.staked -= 1; 119 | } 120 | node.tokens -= 1; 121 | node.reputation -= 1; 122 | } else { 123 | errors.push(format!("{} cannot be punished", node.name)); 124 | } 125 | } 126 | Events::SubmitTask => { 127 | if node_state.task_valid { 128 | node.tokens += 1; 129 | // More staked tokens == higher chance of reputation increasing 130 | if rand::thread_rng().gen_range(node.staked..=node.tokens) == node.tokens { 131 | node.reputation += 1; 132 | } 133 | } else { 134 | if node.can_punish() { 135 | if node.tokens == node.staked { 136 | node.staked -= 1; 137 | } 138 | node.tokens -= 1; 139 | node.reputation -= 1; 140 | } else { 141 | errors.push(format!("{} cannot be punished", node.name)); 142 | } 143 | } 144 | } 145 | _ => { 146 | errors.push(format!("{} transacted with an invalid event", node.name)); 147 | } 148 | }; 149 | } else { 150 | match transaction.event { 151 | Events::UpdateChain => { 152 | // Add node to chain 153 | unique_nodes.push(Node::new(&transaction.name)); 154 | } 155 | _ => { 156 | errors.push(format!("{} not found in chain", transaction.name)); 157 | } 158 | }; 159 | } 160 | } 161 | 162 | if errors.len() == node_state.transactions.len() || unique_nodes.len() == 0 { 163 | return Err(JsError::new("Invalid transactions. No change in chain")); 164 | } 165 | chain.mine_block(unique_nodes); 166 | // let response = Res { chain, errors }; 167 | Ok(JsValue::from_serde(&(chain, errors))?) 168 | } 169 | 170 | /// Validates whether the provided `chain` argument is valid for the latest two blocks in the chain. 171 | /// 172 | /// # Examples 173 | /// 174 | /// ```js 175 | /// const chain = { 176 | /// chain: [ 177 | /// { 178 | /// id: 0, 179 | /// hash: "01101101", 180 | /// previous_hash: "genesis", 181 | /// timestamp: 123456789, 182 | /// data: [ 183 | /// { 184 | /// name: "node_1", 185 | /// tokens: 20, 186 | /// staked: 0, 187 | /// reputation: 1, 188 | /// racks: 0 189 | /// }], 190 | /// nonce: 0, 191 | /// next_miner: "node_1", 192 | /// next_validators: ["node_2"] 193 | /// }], 194 | /// network: ["node_1", "node_2"] 195 | /// }; 196 | /// const isChainValid = handle_validate(chain); 197 | /// assert.equal(isChainValid, true); 198 | /// ``` 199 | /// 200 | /// # Errors 201 | /// 202 | /// If `chain` argument is not deserialisable into type `Chain`, a `JsError` is thrown. 203 | #[wasm_bindgen] 204 | pub fn handle_validate(chain: JsValue) -> Result { 205 | let chain: Chain = chain.into_serde()?; 206 | if let Some(previous_block) = chain.chain.get(chain.chain.len() - 2) { 207 | let last_block: Block = match chain.get_last_block() { 208 | Some(block) => block, 209 | None => return Err(JsError::new("Chain is empty")), 210 | }; 211 | Ok(Node::validate_block(&last_block, previous_block)) 212 | } else { 213 | Err(JsError::new("Chain is too short")) 214 | } 215 | } 216 | 217 | /// Initialise a new blockchain, and returns the corresponding chain. 218 | /// This is only to be called by the first Node starting the network. 219 | #[wasm_bindgen] 220 | pub fn initialise(name: String) -> Result { 221 | let mut chain: Chain = Chain::new(); 222 | 223 | // Create and mine genesis block 224 | let data = vec![Node::new(&name)]; 225 | chain.mine_block(data); 226 | 227 | Ok(JsValue::from_serde(&chain)?) 228 | } 229 | 230 | /// Takes a hash slice, and returns the binary representation. 231 | pub fn hash_to_binary(hash: &[u8]) -> String { 232 | let mut res: String = String::default(); 233 | for c in hash { 234 | res.push_str(&format!("{:b}", c)); 235 | } 236 | res 237 | } 238 | 239 | /// Uses `Sha256` to calculate the hash from a `serde_json::Value` of the input arguments. 240 | pub fn calculate_hash( 241 | data: &Vec, 242 | id: u64, 243 | next_miner: &String, 244 | next_validators: &Vec, 245 | nonce: u64, 246 | previous_hash: &str, 247 | timestamp: u64, 248 | ) -> Vec { 249 | let data = serde_json::json!({ 250 | "id": id, 251 | "previous_hash": previous_hash, 252 | "data": data, 253 | "timestamp": timestamp, 254 | "nonce": nonce, 255 | "next_miner": next_miner, 256 | "next_validators": next_validators, 257 | }); 258 | let mut hasher = Sha256::new(); 259 | hasher.update(data.to_string().as_bytes()); 260 | hasher.finalize().as_slice().to_owned() 261 | } 262 | 263 | #[cfg(test)] 264 | mod tests { 265 | use super::*; 266 | #[test] 267 | fn difficulty_is_not_too_high() { 268 | assert!(DIFFICULTY_PREFIX.len() <= 3); 269 | } 270 | #[test] 271 | fn calculate_hash_works() { 272 | let data = vec![Node::new("test")]; 273 | let hash = calculate_hash( 274 | &data, 275 | 1, 276 | &"test".to_string(), 277 | &vec!["test".to_string()], 278 | 1, 279 | &"test".to_string(), 280 | 1, 281 | ); 282 | assert_eq!(hash.len(), 32); 283 | } 284 | #[test] 285 | fn hash_to_binary_works() { 286 | let hash = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 287 | let hash_str = hash_to_binary(&hash); 288 | assert_eq!(hash_str.len(), 50); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /curriculum/locales/english/build-a-peer-to-peer-network.md: -------------------------------------------------------------------------------- 1 | # Web3 - Build a Peer-to-Peer Network 2 | 3 | ## 1 4 | 5 | ### --description-- 6 | 7 | For this project, you need to create a distrubuted peer-to-peer network in the `build-a-peer-to-peer-network` folder. You are started with some boilerplate code and files, you should not need to change any of the boilerplate code. The `node-1` folder represents a node on your network. The files in it will be cloned and used to run all the other nodes on the network. You only need to change the `index.js` file in there. 8 | 9 | To build the project, use the imported `WebSocket` and `WebSocketServer` variables to create a Web Socket server in the `index.js` file that listens for incoming socket connections and creates a socket connection to all the other nodes on the network. 10 | 11 | To test if your nodes are connecting to each other, run `node clone-node.js` to clone your `node-1`. It will use the next available folder number, and the `PORT` in its `.env` file will correspond to that. e.g. The first time you clone a node, it will create a `node-2` folder with `4002` set as the `PORT`. After that, go into each of your `node-x` folders in their own terminal and run `node index.js` to start each of the servers. If you want to make changes to your node after that, you can run `node delete-nodes.js` to delete all the nodes except `node-1`, then make your changes to `node-1`, and clone it again. 12 | 13 | When you think you are done, run at least three nodes that all connect to each other. 14 | 15 | **User Stories:** 16 | 17 | 1. Your `index.js` should create a web socket server listening on the port in its `.env` file 18 | 19 | 1. When a web socket server starts, it should attempt to open a socket connection to all the addresses in the `known-peers.json` array. Use the predefined `knownPeers` variable 20 | 21 | 1. Whenever a socket connection to a server is established, it should send a message that is a stringified JSON object of `{ type: 'HANDSHAKE', data: }` to it. `data` should be an array of addresses that your server is connected to, including the server's own address 22 | 23 | 1. When a server receives the above message, it should attempt to open a socket connection to all the addresses in the `data` array that it is not already connected to 24 | 25 | 1. You should keep track of all the addresses a server is connected to. You can use the predefined `connectedAddresses` array 26 | 27 | 1. When a socket disconnects, you should remove it from your `connectedAddresses` array 28 | 29 | 1. You should keep track of the servers you are attempting to make a connection to so you don't try to make more than one connection to the same server. You can use the predefined `attemptingToConnectToAddress` variable. Be sure to remove an address after you establish a connection or fail to connect 30 | 31 | 1. A server should never attempt to create a socket connection to its own address 32 | 33 | 1. You should clone your `node-1` folder at least two times 34 | 35 | 1. All of your nodes should have the exact same code, with the exception of the `.env` file 36 | 37 | 1. You should have at least three nodes running, that use ports 4001, 4002, and 4003 38 | 39 | 1. All of your nodes should have an open socket connection to all other nodes 40 | 41 | Bonus: The `add-transaction.js` file is completed so that when you run `node add-transaction.js` from a node-x folder, it sends `{ type: 'TRANSACTION', data: }` to your server. Make it so when a server receives this message, it uses the imported `writeTransactions` function to add it to its local transaction pool (`transactions.json`) and sends the same message to all the other servers, where they do the same thing. You may need to get creative to stop infinite loops of messages. 42 | 43 | Hints: 44 | 45 | - Adding some `console.log` statements when events happen can help 46 | 47 | How to work with a Web Socket Server: 48 | 49 | - `const myServer = new WebSocketServer({ port })` to create a server listening on the given port 50 | - `myServer.on('connection', socket => {})` runs when a socket connects to your server 51 | - `socket.on('message', dataString => {})` runs when a socket sends a message to your server. Nest it within the `connection` function 52 | 53 | How to work with Sockets: 54 | 55 | - `const socket = new WebSocket(address)` to attempt to connect to a server at the given address 56 | - `socket.on('open', () => {})` runs when a connection to a server has been established 57 | - `socket.on('close', () => {})` runs when a socket connection is terminated (connected server gets closed) 58 | - `socket.on('error', () => {})` runs if a connection to server cannot be established 59 | - `socket.send('data')` to send information to a server 60 | 61 | Note: Some of the tests may not pass if the tests before them don't. 62 | 63 | ### --tests-- 64 | 65 | You should not change the `known-peers.json` file. It should have an array with `ws://localhost:4001` and `ws://localhost:4003` values 66 | 67 | ```js 68 | // test 1 69 | const peers = await __helpers.getJsonFile(`${node1Folder}/known-peers.json`); 70 | assert.deepEqual(peers, ['ws://localhost:4001','ws://localhost:4003']); 71 | ``` 72 | 73 | You should have a Web Socket server listening on port 4001 74 | 75 | ```js 76 | // test 2 77 | assert.isTrue(await __helpers.canConnectToSocket('ws://localhost:4001')); 78 | ``` 79 | 80 | When a node on your network receives a message (string) of `{ type: 'HANDSHAKE', data: [] }`, it should create a socket connection to all the addresses in the `data` array that it isn't already connected to 81 | 82 | ```js 83 | // test 3 84 | const res = await __helpers.startSocketServerAndHandshake({ myPort: 4103, connectOnly: true }); 85 | assert.isTrue(res); 86 | ``` 87 | 88 | When a node opens socket connection, it should send a message (string) of `{ type: 'HANDSHAKE', data: }` to the connected node, where `data` is the `connectedAddresses` array with the server's own address included 89 | 90 | ```js 91 | // test 4 92 | const res = await __helpers.startSocketServerAndHandshake({ myPort: 4104 }); 93 | assert.equal(res.type, 'HANDSHAKE'); 94 | assert.typeOf(res.data, 'array'); 95 | assert.include(res.data, 'ws://localhost:4001'); 96 | ``` 97 | 98 | When your web socket server opens a socket connection, you should add the address to the `connectedAddresses` array and include it when sending the `HANDSHAKE` message 99 | 100 | ```js 101 | // test 5 102 | const res = await __helpers.startSocketServerAndHandshake({ myPort: 4105 }); 103 | assert.include(res.data, 'ws://localhost:4105'); 104 | ``` 105 | 106 | When one of your server's socket connections closes, you should remove the address from the `connectedAddresses` array and exclude it when sending the `HANDSHAKE` message 107 | 108 | ```js 109 | // test 6 110 | const res1 = await __helpers.startSocketServerAndHandshake({ myPort: 4106 }); 111 | const res2 = await __helpers.startSocketServerAndHandshake({ myPort: 4206 }); 112 | 113 | assert.include(res1.data, 'ws://localhost:4106', 'Your handshake message should include an open socket address'); 114 | assert.notInclude(res2.data, 'ws://localhost:4106', 'Your handshake message should not include a closed socket address'); 115 | ``` 116 | 117 | You should have a Web Socket server listening on port 4002 118 | 119 | ```js 120 | // test 7 121 | assert.isTrue(await __helpers.canConnectToSocket('ws://localhost:4002')); 122 | ``` 123 | 124 | You should have a Web Socket server listening on port 4003 125 | 126 | ```js 127 | // test 8 128 | assert.isTrue(await __helpers.canConnectToSocket('ws://localhost:4003')); 129 | ``` 130 | 131 | All of your `node-x` folders (nodes) should have the exact same code in their `index.js` file as the `node-1/index.js` file 132 | 133 | ```js 134 | // test 9 135 | const node1Index = await __helpers.getFile(`${node1Folder}/index.js`); 136 | const projectDirectory = await __helpers.getDirectory(projectFolder); 137 | const nodes = projectDirectory.filter(file => /^node-\d+$/.test(file)); 138 | 139 | for(let i=0; i= 25 { 220 | break false; 221 | } 222 | c += 1; 223 | }; 224 | assert!(a, "probability-based test - may fail"); 225 | } 226 | 227 | #[wasm_bindgen_test] 228 | fn submit_task_few_staked() { 229 | let mut camper = Node::new("Camper"); 230 | camper.staked = 1; 231 | let data = vec![camper]; 232 | let mut fix_node_state = fix(data); 233 | fix_node_state.transactions.push(Transaction { 234 | name: "Camper".to_string(), 235 | event: Events::Stake, 236 | }); 237 | fix_node_state.transactions.push(Transaction { 238 | name: "Camper".to_string(), 239 | event: Events::Stake, 240 | }); 241 | // mine until reputation increases 242 | let mut c = 0; 243 | let mut l = 0; 244 | let a = loop { 245 | let (chain, _) = mine(fix_node_state.clone()).expect("result to be chain"); 246 | if chain.get_node_by_name("Camper").unwrap().reputation == 1 { 247 | c += 1; 248 | } 249 | if c >= 10 { 250 | break true; 251 | } 252 | if l >= 25 { 253 | break false; 254 | } 255 | l += 1; 256 | }; 257 | assert!(a, "probability-based test - may fail"); 258 | } 259 | 260 | #[wasm_bindgen_test] 261 | fn submit_task_rep_chance() { 262 | let camper = Node::new("Camper"); 263 | let data = vec![camper]; 264 | let mut fix_node_state = fix(data); 265 | fix_node_state.transactions.push(Transaction { 266 | name: "Camper".to_string(), 267 | event: Events::Stake, 268 | }); 269 | fix_node_state.transactions.push(Transaction { 270 | name: "Camper".to_string(), 271 | event: Events::Stake, 272 | }); 273 | let (chain, _) = mine(fix_node_state).expect("result to be chain"); 274 | assert_eq!(chain.get_node_by_name("Camper").unwrap().tokens, 21); 275 | } 276 | 277 | fn fix(data: Vec) -> NodeState { 278 | let node_vec_str = serde_json::to_string(&data).unwrap(); 279 | let fix_node_state = format!( 280 | r#"{{ 281 | "chain": {{ 282 | "chain": [ 283 | {{ 284 | "id": 0, 285 | "hash": "00110101", 286 | "previous_hash": "", 287 | "timestamp": 123456789, 288 | "data": {}, 289 | "nonce": 123, 290 | "next_miner": "Camper", 291 | "next_validators": ["Camper"] 292 | }} 293 | ], 294 | "network": ["Camper"] 295 | }}, 296 | "transactions": [ 297 | {{ 298 | "event": "SubmitTask", 299 | "name": "Camper" 300 | }} 301 | ], 302 | "task_valid": true 303 | }}"#, 304 | node_vec_str 305 | ); 306 | serde_json::from_str(&fix_node_state).unwrap() 307 | } 308 | 309 | fn mine(fix_node_state: NodeState) -> Result<(Chain, Vec), JsValue> { 310 | let node_state = JsValue::from_serde(&fix_node_state).unwrap(); 311 | let res = handle_mine(node_state); 312 | let response = match res { 313 | Ok(v) => match v.into_serde() { 314 | Ok(v) => v, 315 | Err(e) => { 316 | console::log_1(&format!("{:?}", e).into()); 317 | panic!("could not serde response"); 318 | } 319 | }, 320 | Err(e) => { 321 | // Error is converted into a JsValue to make use of Debug trait 322 | return Err(JsValue::from(e)); 323 | } 324 | }; 325 | Ok(response) 326 | } 327 | --------------------------------------------------------------------------------