Lab 1
This commit is contained in:
14
exam/common.js
Normal file
14
exam/common.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
export function installDeps() {
|
||||
// check if node_modules exists
|
||||
if (fs.existsSync('./node_modules')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// install dependencies
|
||||
console.log('Installing dependencies...');
|
||||
execSync('npm install', { stdio: 'inherit' });
|
||||
console.log('Dependencies installed');
|
||||
}
|
||||
54
exam/exam.js
Normal file
54
exam/exam.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { installDeps } from './common.js';
|
||||
|
||||
const SERVER_PORT = 3018;
|
||||
|
||||
async function main() {
|
||||
installDeps();
|
||||
|
||||
const parentDirectory = process.cwd();
|
||||
const indexHtml = fs.readFileSync(path.join(parentDirectory, 'exam', 'index.html'), 'utf8');
|
||||
const examTheory = fs.readFileSync(path.join(parentDirectory, 'exam', 'theory.json'), 'utf8');
|
||||
let examTheoryData = JSON.parse(examTheory);
|
||||
|
||||
// import express and start the server
|
||||
const express = await import('express');
|
||||
const app = express.default();
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
res.send(indexHtml);
|
||||
});
|
||||
|
||||
app.get('/theory', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.json(examTheoryData);
|
||||
});
|
||||
|
||||
app.post('/theory-answer', (req, res) => {
|
||||
const questionId = req.body.questionId;
|
||||
const answer = req.body.answer;
|
||||
const index = examTheoryData.findIndex(q => q.id === questionId);
|
||||
if (index !== -1) {
|
||||
examTheoryData[index].answer = answer;
|
||||
fs.writeFileSync(path.join(parentDirectory, 'exam', 'theory.json'), JSON.stringify(examTheoryData, null, 2));
|
||||
res.status(200).json({ success: true });
|
||||
} else {
|
||||
res.status(404).json({ success: false, error: 'Question not found' });
|
||||
}
|
||||
});
|
||||
|
||||
const open = await import('open');
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log(`Server is running on port ${SERVER_PORT}`);
|
||||
open.default(`http://localhost:${SERVER_PORT}/`);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
425
exam/index.html
Normal file
425
exam/index.html
Normal file
@@ -0,0 +1,425 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
|
||||
>
|
||||
<style>
|
||||
[data-theme="dark"] {
|
||||
--pico-background-color: #282a36; /* Custom color */
|
||||
}
|
||||
|
||||
.app-header {
|
||||
border-bottom: 1px solid var(--pico-muted-border-color);
|
||||
box-shadow: 0 1px 0 var(--pico-muted-border-color);
|
||||
}
|
||||
|
||||
[data-theme="light"] .app-header {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .app-header {
|
||||
background-color: rgba(9, 21, 21, 0.6);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .sidebar {
|
||||
border-right: 3px solid rgba(9, 21, 21, 0.6);
|
||||
}
|
||||
|
||||
.app-header nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.app-header hgroup {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-header #theme-icon {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.app-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.app-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sidebar {
|
||||
width: 315px;
|
||||
flex-shrink: 0;
|
||||
padding: 1.5rem;
|
||||
border-right: 1px solid var(--pico-muted-border-color);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sidebar nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.sidebar .sidebar-button {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem 0.75rem 0;
|
||||
border-bottom: 1px solid var(--pico-muted-border-color);
|
||||
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] hgroup>:not(:first-child):last-child {
|
||||
--pico-color: revert;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content section.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-content section p.mt-3 {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.main-content section .theory-question:first-of-type {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.main-content section .theory-question {
|
||||
border-top: 1px solid var(--pico-muted-border-color);
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.main-content section .theory-question p.question {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.main-content section hgroup p {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
/* Mobile: hide sidebar, show desktop-only message */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
.main-content {
|
||||
display: none;
|
||||
}
|
||||
.mobile-message {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
.mobile-message {
|
||||
display: none;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.mobile-message article {
|
||||
max-width: 28rem;
|
||||
}
|
||||
</style>
|
||||
<title>Lab 1</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-root">
|
||||
<header class="app-header">
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<hgroup>
|
||||
<h2>Lab 1</h2>
|
||||
<p>Distributed Systems and Blockchain</p>
|
||||
</hgroup>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<details class="dropdown">
|
||||
<summary>
|
||||
Documentation
|
||||
</summary>
|
||||
<ul>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/viem-docs.pdf" target="_blank">Viem</a></li>
|
||||
<li><a href="https://api.next.code-camp.org/cdn/solidity-docs.pdf" target="_blank">Solidity</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span id="theme-icon" aria-hidden="true" onclick="cycleTheme()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32" fill="currentColor" class="icon-theme-toggle "><clipPath id="theme-toggle-cutout"><path d="M0-11h25a1 1 0 0017 13v30H0Z"></path></clipPath><g clip-path="url(#theme-toggle-cutout)"><circle cx="16" cy="16" r="8.4"></circle><path d="M18.3 3.2c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3S14.7.9 16 .9s2.3 1 2.3 2.3zm-4.6 25.6c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3-1 2.3-2.3 2.3-2.3-1-2.3-2.3zm15.1-10.5c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zM3.2 13.7c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3S.9 17.3.9 16s1-2.3 2.3-2.3zm5.8-7C9 7.9 7.9 9 6.7 9S4.4 8 4.4 6.7s1-2.3 2.3-2.3S9 5.4 9 6.7zm16.3 21c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zm2.4-21c0 1.3-1 2.3-2.3 2.3S23 7.9 23 6.7s1-2.3 2.3-2.3 2.4 1 2.4 2.3zM6.7 23C8 23 9 24 9 25.3s-1 2.3-2.3 2.3-2.3-1-2.3-2.3 1-2.3 2.3-2.3z"></path></g></svg>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="app-body">
|
||||
<aside class="sidebar">
|
||||
<nav>
|
||||
<a href="#theory" class="sidebar-button">Theory</a>
|
||||
<a href="#viem" class="sidebar-button">Coding - Viem</a>
|
||||
<a href="#hardhat" class="sidebar-button">Coding - Hardhat</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content container" id="main-content">
|
||||
<section id="theory">
|
||||
<hgroup>
|
||||
<h2>Theory</h1>
|
||||
<p>Answer the following questions to the best of your ability. Each question has exactly one correct answer. There are no negative marks for incorrect answers.</p>
|
||||
</hgroup>
|
||||
|
||||
|
||||
<div id="theory-questions"></div>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="viem" class="hidden">
|
||||
<h2>Coding - Viem: Transaction Explorer</h1>
|
||||
|
||||
<p>In the repository you downloaded, there is a directory called <strong>/scripts</strong>. Inside, there is a file called <strong>transaction-explorer.ts</strong>, which is used to analyze a transaction on the main Ethereum network. Your job is to complete the script so that it prints the following information for each transaction:</p>
|
||||
|
||||
<ul>
|
||||
<li>The transaction hash</li>
|
||||
<li>The block number</li>
|
||||
<li>The transaction sender (as an address)</li>
|
||||
<li>The current balance (in ETH) of the sender (as read from the blockchain)</li>
|
||||
<li>The transaction receiver (as an address)</li>
|
||||
<li>The current balance (in ETH) of the receiver (as read from the blockchain)</li>
|
||||
<li>The transaction amount (in ETH)</li>
|
||||
<li>The transaction fee, i.e. the effective gas price multiplied by the gas used (shown in both ETH and Gwei)</li>
|
||||
<li>The transaction status (for example, if it's successful or reverted)</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-3">You can test the script by running the following command:</p>
|
||||
<pre><code>npx tsx scripts/transaction-explorer.ts</code></pre>
|
||||
|
||||
<p class="mt-3">Here are two example transactions (one successful and one failed), which you can use to test your script:</p>
|
||||
<div class="overflow-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Transaction Hash</th>
|
||||
<th>Etherscan Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="word-break: break-all;">0x078a1bbbf7cb870db6fc02e7eaf843600754a6758a062cd85fe24ee065ac4e9c</td>
|
||||
<td><a href="https://etherscan.io/tx/0x078a1bbbf7cb870db6fc02e7eaf843600754a6758a062cd85fe24ee065ac4e9c" target="_blank">View on Etherscan</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="word-break: break-all;">0xf87645aa0740109eb9bd8a59525572faf39d508681c7ffd073f763fb94ceea58</td>
|
||||
<td><a href="https://etherscan.io/tx/0xf87645aa0740109eb9bd8a59525572faf39d508681c7ffd073f763fb94ceea58" target="_blank">View on Etherscan</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="mt-3">Hint: you can start with the getTransaction function from the Viem library, and analyze its output with console.log(). Note that you will need to use additional functions to retrieve all the required information.</p>
|
||||
</section>
|
||||
|
||||
|
||||
<section id="hardhat" class="hidden">
|
||||
<h2>Coding - Hardhat: Subscription Contract</h1>
|
||||
|
||||
<p>In the repository you downloaded, there is a directory called <strong>/contracts</strong> (for storing smart contracts), and a directory called <strong>/test</strong> (for test files). In the first directory, there is a file called <strong>Subscription.sol</strong> (where you need to write a smart contract in Solidity); while in the second directory, there is a file called <strong>Subscription.ts</strong> (where you need to write tests for the contract).</p>
|
||||
<p>The contract is meant to be used by a company to track lifetime subscriptions for their customers. It needs to implement the following functionality:</p>
|
||||
|
||||
<ul>
|
||||
<li>Ability for accounts to purchase a standard subscription for 1 ETH.</li>
|
||||
<li>Ability for accounts who already have a subscription to upgrade to a premium subscription for +5 ETH.</li>
|
||||
<li>Ability for the creator of the contract to withdraw the funds from the contract.</li>
|
||||
<li>A view function to get the current subscription status of an address (which should return either: "standard", "premium", or "none").</li>
|
||||
<li>The contract should validate the inputs and payments appropriately, and return errors if something is not correct.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<p class="mt-3">You can build the contracts by running the following command:</p>
|
||||
<pre><code>npx hardhat build</code></pre>
|
||||
|
||||
<p class="mt-3">Once you start adding tests, you can run them with the following command:</p>
|
||||
<pre><code>npx hardhat test</code></pre>
|
||||
|
||||
<p class="mt-3">Hint: you have an example contract and tests (see Counter.sol and Counter.ts) to help you get started. To optimize storage and gas, you should use an integer to store the subscription type in a mapping (or use two mappings which store a boolean).</p>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="mobile-message">
|
||||
<article>
|
||||
<header>
|
||||
<strong>Desktop required</strong>
|
||||
</header>
|
||||
<p>This exam layout needs a larger screen. Please open it on a desktop computer.</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function setupTheme() {
|
||||
var THEME_KEY = 'dsb-exam-theme';
|
||||
var html = document.documentElement;
|
||||
var icon = document.getElementById('theme-icon');
|
||||
|
||||
function applyTheme(theme) {
|
||||
html.setAttribute('data-theme', theme);
|
||||
try { localStorage.setItem(THEME_KEY, theme); } catch (e) {}
|
||||
}
|
||||
|
||||
function cycleTheme(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(next);
|
||||
}
|
||||
|
||||
try {
|
||||
var saved = localStorage.getItem(THEME_KEY);
|
||||
if (saved === 'light' || saved === 'dark') applyTheme(saved);
|
||||
} catch (e) {}
|
||||
|
||||
if (icon) icon.addEventListener('click', cycleTheme);
|
||||
}
|
||||
|
||||
function setupSidebar() {
|
||||
var sidebar = document.querySelector('.sidebar');
|
||||
var sidebarButtons = sidebar.querySelectorAll('.sidebar-button');
|
||||
sidebarButtons.forEach(function(button) {
|
||||
button.addEventListener('click', function() {
|
||||
var target = button.getAttribute('href');
|
||||
var targetSection = document.querySelector(target);
|
||||
|
||||
var sections = document.querySelectorAll('.main-content section');
|
||||
|
||||
if (targetSection) {
|
||||
document.body.style.opacity = 0;
|
||||
sections.forEach(function(section) {
|
||||
section.classList.add('hidden');
|
||||
});
|
||||
|
||||
targetSection.classList.remove('hidden');
|
||||
setTimeout(function() {
|
||||
window.scrollTo(0, 0);
|
||||
document.body.style.opacity = 1;
|
||||
}, 50);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupTheory() {
|
||||
|
||||
function setupTheoryCheckboxes() {
|
||||
var theoryCheckboxes = document.querySelectorAll('.theory-question input[type="checkbox"]');
|
||||
theoryCheckboxes.forEach(function(checkbox) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
var question = checkbox.closest('.theory-question');
|
||||
var questionId = question.getAttribute('id').split('-')[1];
|
||||
var answer = checkbox.getAttribute('name').split('-')[2];
|
||||
var otherCheckboxes = question.querySelectorAll('input[type="checkbox"]');
|
||||
otherCheckboxes.forEach(function(otherCheckbox) {
|
||||
const otherAnswer = otherCheckbox.getAttribute('name').split('-')[2];
|
||||
if (otherAnswer !== answer) {
|
||||
otherCheckbox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
checkbox.checked = true;
|
||||
fetch('/theory-answer', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
questionId: Number(questionId),
|
||||
answer: answer
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(function(response) {
|
||||
if (!response.ok) {
|
||||
alert('Failed to submit answer.');
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
alert('Failed to submit answer.');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
fetch('/theory')
|
||||
.then(response => response.json())
|
||||
.then(function(data) {
|
||||
var theory = document.querySelector('#theory');
|
||||
|
||||
var html = '';
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var questionId = data[i].id;
|
||||
var question = data[i].question;
|
||||
var options = data[i].options;
|
||||
var answer = data[i].answer;
|
||||
|
||||
html += '<div class="theory-question" id="question-' + questionId + '">';
|
||||
html += '<p class="question">' + (i + 1) + '. ' + question + "</p>";
|
||||
const sortedOptionKeys = Object.keys(options).sort();
|
||||
|
||||
html += '<fieldset>';
|
||||
sortedOptionKeys.forEach(function(optionKey) {
|
||||
html += '<label>';
|
||||
html += '<input type="checkbox" name="question-' + questionId + '-' + optionKey + '"' + (answer === optionKey ? ' checked' : '') +'>';
|
||||
html += ' ' + options[optionKey];
|
||||
html += '</label>';
|
||||
});
|
||||
html += '</fieldset>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
document.querySelector('#theory-questions').innerHTML = html;
|
||||
setupTheoryCheckboxes();
|
||||
});
|
||||
}
|
||||
|
||||
setupTheme();
|
||||
setupSidebar();
|
||||
setupTheory();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
122
exam/theory.json
Normal file
122
exam/theory.json
Normal file
@@ -0,0 +1,122 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"question": "What is a distributed system?",
|
||||
"options": {
|
||||
"A": "A single powerful computer running multiple programs",
|
||||
"B": "A collection of multiple computers that appears as one system",
|
||||
"C": "A system that only runs on cloud providers",
|
||||
"D": "A decentralized blockchain network"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"question": "What is horizontal scaling?",
|
||||
"options": {
|
||||
"A": "Adding more CPU and RAM to a single machine",
|
||||
"B": "Replacing hardware with faster SSDs",
|
||||
"C": "Adding more machines to improve performance",
|
||||
"D": "Upgrading the operating system"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"question": "Which is true about distributed systems compared to monolithic systems?",
|
||||
"options": {
|
||||
"A": "They are more resilient but more complex",
|
||||
"B": "They always have a single point of failure",
|
||||
"C": "They cannot scale horizontally",
|
||||
"D": "They require downtime for maintenance"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"question": "Which statement about distributed vs decentralized systems is correct?",
|
||||
"options": {
|
||||
"A": "Every distributed system is decentralized",
|
||||
"B": "A decentralized system must be distributed",
|
||||
"C": "A distributed system must have no central authority",
|
||||
"D": "They are identical concepts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"question": "According to the Fallacies of Distributed Computing, which of these is a false assumption developers often make?",
|
||||
"options": {
|
||||
"A": "The network is reliable",
|
||||
"B": "Latency is zero",
|
||||
"C": "Bandwidth is infinite",
|
||||
"D": "All of the above"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"question": "In Proof of Work (PoW), miners:",
|
||||
"options": {
|
||||
"A": "Stake their coins",
|
||||
"B": "Solve cryptographic puzzles",
|
||||
"C": "Vote on transactions",
|
||||
"D": "Approve all transactions"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"question": "What is the smallest unit of Bitcoin?",
|
||||
"options": {
|
||||
"A": "Wei",
|
||||
"B": "Gwei",
|
||||
"C": "Ether",
|
||||
"D": "Satoshi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"question": "What is Gas in Ethereum?",
|
||||
"options": {
|
||||
"A": "A blockchain explorer",
|
||||
"B": "A wallet feature",
|
||||
"C": "The fee required to execute transactions",
|
||||
"D": "A mining pool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"question": "A crypto wallet actually stores:",
|
||||
"options": {
|
||||
"A": "The coins themselves",
|
||||
"B": "A copy of the blockchain",
|
||||
"C": "Private and public keys",
|
||||
"D": "Mining rewards"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"question": "With synchronous communication:",
|
||||
"options": {
|
||||
"A": "The sender does not wait",
|
||||
"B": "The sender waits for a response",
|
||||
"C": "All messages go through a broker",
|
||||
"D": "There is no temporal coupling"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"question": "In basic Pub/Sub, when a message is published to a topic:",
|
||||
"options": {
|
||||
"A": "The broker deletes it immediately",
|
||||
"B": "The producer processes it",
|
||||
"C": "Only one subscriber receives the message (Point-to-Point)",
|
||||
"D": "All subscribers interested in that topic receive a copy"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"question": "What is exponential backoff used for?",
|
||||
"options": {
|
||||
"A": "Waiting progressively longer between retries",
|
||||
"B": "Synchronizing clocks",
|
||||
"C": "Reducing memory usage",
|
||||
"D": "Increasing blockchain throughput"
|
||||
}
|
||||
}
|
||||
]
|
||||
62
exam/zip.js
Normal file
62
exam/zip.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { installDeps } from './common.js';
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
|
||||
async function main() {
|
||||
installDeps();
|
||||
|
||||
const prompts = await import('prompts');
|
||||
console.log(`Lets create a zip archive for your submission.`);
|
||||
const { studentId } = await prompts.default({
|
||||
type: 'text',
|
||||
name: 'studentId',
|
||||
message: 'What is your student ID?',
|
||||
format: (value) => value.trim().replace(/[^A-Za-z0-9]/g, ''),
|
||||
});
|
||||
|
||||
if (!studentId) {
|
||||
console.error('Student ID is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const parentDirectory = process.cwd();
|
||||
const archiveName = `${packageJson.name}-${studentId}.zip`;
|
||||
const zipPath = path.join(parentDirectory, archiveName);
|
||||
|
||||
if (fs.existsSync(zipPath)) {
|
||||
console.log(`Removing existing archive at ${zipPath}`);
|
||||
fs.unlinkSync(zipPath);
|
||||
}
|
||||
|
||||
const archiver = await import('archiver');
|
||||
const archive = archiver.default('zip');
|
||||
archive.on('error', (err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
archive.glob('**/*', {
|
||||
cwd: parentDirectory,
|
||||
ignore: [
|
||||
'node_modules/**',
|
||||
'.git/**',
|
||||
'artifacts/**',
|
||||
'cache/**',
|
||||
'.env',
|
||||
'.DS_Store',
|
||||
'*.zip',
|
||||
'exam/common.js',
|
||||
'exam/exam.js',
|
||||
'exam/index.html',
|
||||
'exam/zip.js'
|
||||
]
|
||||
|
||||
});
|
||||
archive.pipe(fs.createWriteStream(zipPath));
|
||||
await archive.finalize();
|
||||
|
||||
console.log();
|
||||
console.log(`Archive created: ${zipPath}`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user