Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
node_modules
yarn.lock
.cache
.parcel-cache
*.lock
dist
29 changes: 29 additions & 0 deletions client/explorer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<html>
<body>
<h1>
Block Explorer
</h1>

<span>Block Number: </span><input id="blockNumber"></input>
<button id="get-block">
Submit
</button>

<p></p>
<div id="block">
<div>
<span>Timestamp: </span><span id="timestamp"></span>
</div>
<div>
<span>Nonce: </span><span id="nonce"></span>
</div>
<h2>
Transactions:
</h2>
<div id="transactions">
</div>
</div>

<script type="module" src="./explorer.js"></script>
</body>
</html>
49 changes: 49 additions & 0 deletions client/explorer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import "./explorer.scss";

document.getElementById("get-block").addEventListener('click', () => {
const params = {
method: "getBlock",
params: [document.getElementById("blockNumber").value],
jsonrpc: "2.0",
id: 1
}
const request = new Request('http://localhost:3032/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
});

fetch(request)
.then(response => {
return response.json();
}).then(({result}) => {
console.log(result)
block = JSON.parse(result);
let date = new Date(block.timestamp);
let formattedTime = date.toLocaleDateString("en-US") + " " + date.toLocaleTimeString("en-US");
document.getElementById("timestamp").innerHTML = formattedTime;
document.getElementById("nonce").innerHTML = block.nonce;

let txList = "";
let txn = 0;
for (let tx of block.transactions) {
let inputList = "";
for (let input of tx.inputs) {
inputList += `<li>${input.owner}: ${input.amount}</li>\n`
}
let inputHtml = `<ul>${inputList}</ul>`

let outputList = "";
for (let output of tx.outputs) {
outputList += `<li>${output.owner}: ${output.amount}</li>\n`
}
let outputHtml = `<ul>${outputList}</ul>`

txList += `<div><h3>Transaction ${txn}</h3><li>Inputs<div id="inputs">${inputHtml}</div>Outputs<div id="outputs">${outputHtml}</div></li></div>\n`;
txn++;
}
let txHtml = `<ul>${txList}</ul>`
document.getElementById("transactions").innerHTML = txHtml;
console.log(txHtml)
});
});
21 changes: 21 additions & 0 deletions client/explorer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
html {
background-color: white;
}

body {
margin: 10px;
padding: 20px;
background-color: #eee;
border-radius: 20px;
}

.button {
margin: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: white;
cursor: pointer;
text-transform: uppercase;
}

2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ <h2>
0
</div>

<script src="./index.js"></script>
<script type="module" src="./index.js"></script>
</body>
</html>
37 changes: 32 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
const {startMining, stopMining} = require('./mine');
const {startMining, stopMining, addTransaction} = require('./mine');
const {PORT} = require('./config');
const {utxos, blockchain} = require('./db');
const express = require('express');
const app = express();
const cors = require('cors');
const UTXO = require('./models/UTXO');
const Transaction = require('./models/Transaction');


// localhost can have cross origin errors
// depending on the browser you use!
app.use(cors());
app.use(express.json());

const getUTXOsByAddress = (address) => {
return utxos.filter(x => {
return x.owner === address && !x.spent;
});
}
const getBalance = (address) => {
return getUTXOsByAddress(address).reduce((p,c) => p + c.amount, 0);
}

app.post('/', (req, res) => {
const {method, params} = req.body;
if(method === 'startMining') {
Expand All @@ -24,12 +36,27 @@ app.post('/', (req, res) => {
}
if(method === "getBalance") {
const [address] = params;
const ourUTXOs = utxos.filter(x => {
return x.owner === address && !x.spent;
});
const sum = ourUTXOs.reduce((p,c) => p + c.amount, 0);
const sum = getBalance(address);
res.send({ balance: sum.toString()});
}
if(method === 'addTransaction') {
let [from, to, amount, fee] = params;
if (amount + fee <= getBalance(from)) {
inputs = getUTXOsByAddress(from);
outputs = [new UTXO(to, amount), new UTXO(from, getBalance(from) - amount - fee)];
addTransaction(new Transaction(inputs, outputs));
res.send({result: "Success"});
} else {
res.send({result: "Not enough funds."});
}
return;
}
if(method === "getBlock") {
const [blockNumber] = params;
block = blockchain.getBlock(blockNumber);
console.log(block)
res.send({result: JSON.stringify(block)});
}
});

app.listen(PORT, () => {
Expand Down
26 changes: 21 additions & 5 deletions mine.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const db = require('./db');
const {PUBLIC_KEY} = require('./config');
const TARGET_DIFFICULTY = BigInt("0x0" + "F".repeat(63));
const BLOCK_REWARD = 10;
const MAX_TRANSACTIONS = 10;
const mempool = [];

let mining = true;
mine();
Expand All @@ -18,14 +20,22 @@ function stopMining() {
mining = false;
}

function addTransaction(tx) {
mempool.push(tx)
}

function mine() {
if(!mining) return;

const block = new Block(db.blockchain.lastHash());

const blockFees = mempool.map(x => x.getFee()).reduce((a,b) => a + b, 0);

const block = new Block();

// TODO: add transactions from the mempool
while ((mempool.length > 0) & (block.transactions.length < MAX_TRANSACTIONS)) {
block.addTransaction(mempool.pop());
}

const coinbaseUTXO = new UTXO(PUBLIC_KEY, BLOCK_REWARD);
const coinbaseUTXO = new UTXO(PUBLIC_KEY, BLOCK_REWARD + blockFees);
const coinbaseTX = new Transaction([], [coinbaseUTXO]);
block.addTransaction(coinbaseTX);

Expand All @@ -37,12 +47,18 @@ function mine() {

db.blockchain.addBlock(block);

console.log(`Mined block #${db.blockchain.blockHeight()} with a hash of ${block.hash()} at nonce ${block.nonce}`);
console.log(`
Mined block #${db.blockchain.blockHeight()},
hash: ${block.hash()},
nonce: ${block.nonce},
previous hash: ${block.previousHash},
transactions: ${JSON.stringify(block.transactions)}`);

setTimeout(mine, 2500);
}

module.exports = {
startMining,
stopMining,
addTransaction
};
6 changes: 4 additions & 2 deletions models/Block.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const SHA256 = require('crypto-js/sha256');

class Block {
constructor() {
constructor(previousHash) {
this.timestamp = Date.now();
this.nonce = 0;
this.transactions = [];
this.previousHash = previousHash;
}
addTransaction(tx) {
this.transactions.push(tx);
Expand All @@ -13,7 +14,8 @@ class Block {
return SHA256(
this.timestamp + "" +
this.nonce + "" +
JSON.stringify(this.transactions)
JSON.stringify(this.transactions) +
this.previousHash
).toString();
}
execute() {
Expand Down
16 changes: 15 additions & 1 deletion models/Blockchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,21 @@ class Blockchain {
this.blocks.push(block);
}
blockHeight() {
return this.blocks.length;
return this.blocks.length - 1;
}
lastHash() {
if (this.blockHeight() > 0) {
return this.blocks[this.blockHeight()].hash();
} else {
return 0;
}
}
getBlock(blockNumber) {
if (blockNumber <= this.blockHeight()) {
return this.blocks[blockNumber];
} else {
return null;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions models/Transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class Transaction {
utxos.push(output);
});
}
getFee() {
return this.inputs.reduce((p,c) => p + c.amount, 0) - this.outputs.reduce((p,c) => p + c.amount, 0);
}
}

module.exports = Transaction;
Loading