Skip to content

first #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
18 changes: 18 additions & 0 deletions rfc-1/dist/central-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// src/central-server.ts
import { WebSocketServer, WebSocket } from 'ws';
const wss = new WebSocketServer({ port: 6001 });
console.log('Central Relay Server started on port 6001');
wss.on('connection', (ws) => {
console.log('A new miner has connected.');
ws.on('message', (message) => {
// Broadcast the message to every other connected client
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.on('close', () => {
console.log('A miner has disconnected.');
});
});
160 changes: 160 additions & 0 deletions rfc-1/dist/lib/blockchain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// src/lib/blockchain.ts
import SHA256 from 'crypto-js/sha256.js';
import elliptic from 'elliptic';
const ec = new elliptic.ec('secp256k1');
export class Transaction {
constructor(fromAddress, toAddress, amount) {
this.fromAddress = fromAddress;
this.toAddress = toAddress;
this.amount = amount;
this.timestamp = Date.now();
this.signature = '';
}
calculateHash() {
return SHA256(this.fromAddress + this.toAddress + this.amount + this.timestamp).toString();
}
signTransaction(signingKey) {
if (signingKey.getPublic('hex') !== this.fromAddress) {
throw new Error('You cannot sign transactions for other wallets!');
}
const hashTx = this.calculateHash();
const sig = signingKey.sign(hashTx, 'base64');
this.signature = sig.toDER('hex');
}
isValid() {
if (this.fromAddress === null)
return true; // Mining reward transaction
if (!this.signature || this.signature.length === 0) {
throw new Error('No signature in this transaction');
}
const publicKey = ec.keyFromPublic(this.fromAddress, 'hex');
return publicKey.verify(this.calculateHash(), this.signature);
}
}
export class Block {
constructor(timestamp, transactions, previousHash = '') {
this.timestamp = timestamp;
this.transactions = transactions;
this.previousHash = previousHash;
this.nonce = 0;
this.hash = this.calculateHash();
}
calculateHash() {
return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}
mineBlock(difficulty) {
while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) {
this.nonce++;
this.hash = this.calculateHash();
}
console.log(`BLOCK MINED: ${this.hash}`);
}
hasValidTransactions() {
for (const tx of this.transactions) {
// We need to re-create the transaction instance to use its methods
const txInstance = new Transaction(tx.fromAddress, tx.toAddress, tx.amount);
txInstance.signature = tx.signature;
txInstance.timestamp = tx.timestamp;
if (!txInstance.isValid()) {
return false;
}
}
return true;
}
}
export class Blockchain {
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 2;
this.pendingTransactions = [];
this.miningReward = 100;
}
createGenesisBlock() {
return new Block(Date.now(), [], '0');
}
getLatestBlock() {
return this.chain[this.chain.length - 1];
}
minePendingTransactions(miningRewardAddress) {
const rewardTx = new Transaction(null, miningRewardAddress, this.miningReward);
this.pendingTransactions.push(rewardTx);
const block = new Block(Date.now(), this.pendingTransactions, this.getLatestBlock().hash);
block.mineBlock(this.difficulty);
console.log('Block successfully mined!');
this.chain.push(block);
this.pendingTransactions = [];
return block;
}
addTransaction(transaction) {
if (!transaction.fromAddress || !transaction.toAddress) {
throw new Error('Transaction must include from and to address');
}
if (!transaction.isValid()) {
throw new Error('Cannot add invalid transaction to chain');
}
const walletBalance = this.getBalanceOfAddress(transaction.fromAddress);
if (walletBalance < transaction.amount) {
throw new Error('Not enough balance');
}
// Prevent duplicate transactions in the pending pool
if (this.pendingTransactions.some(tx => tx.signature === transaction.signature)) {
console.log('Duplicate transaction ignored.');
return;
}
this.pendingTransactions.push(transaction);
console.log('Transaction added to pending pool.');
}
getBalanceOfAddress(address) {
let balance = 0;
for (const block of this.chain) {
for (const trans of block.transactions) {
if (trans.fromAddress === address) {
balance -= trans.amount;
}
if (trans.toAddress === address) {
balance += trans.amount;
}
}
}
return balance;
}
isChainValid(chain = this.chain) {
for (let i = 1; i < chain.length; i++) {
const currentBlock = new Block(chain[i].timestamp, chain[i].transactions, chain[i].previousHash);
currentBlock.hash = chain[i].hash;
currentBlock.nonce = chain[i].nonce;
const previousBlock = chain[i - 1];
if (!currentBlock.hasValidTransactions())
return false;
if (currentBlock.hash !== currentBlock.calculateHash())
return false;
if (currentBlock.previousHash !== previousBlock.hash)
return false;
}
return true;
}
replaceChain(newChain) {
if (newChain.length <= this.chain.length) {
console.log('Received chain is not longer than the current chain.');
return;
}
if (!this.isChainValid(newChain)) {
console.log('The received chain is not valid.');
return;
}
console.log('Replacing current chain with the new one.');
// Re-instantiate class instances from plain objects
this.chain = newChain.map(blockData => {
const block = new Block(blockData.timestamp, [], blockData.previousHash);
block.hash = blockData.hash;
block.nonce = blockData.nonce;
block.transactions = blockData.transactions.map(txData => {
const tx = new Transaction(txData.fromAddress, txData.toAddress, txData.amount);
tx.timestamp = txData.timestamp;
tx.signature = txData.signature;
return tx;
});
return block;
});
}
}
67 changes: 67 additions & 0 deletions rfc-1/dist/miner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// src/miner.ts
import express from 'express';
import cors from 'cors';
import WebSocket from 'ws';
import { Blockchain, Transaction } from './lib/blockchain.js';
// --- CONFIGURATION ---
const http_port = process.argv[2] || 3001;
const miner_address = process.argv[3] || `miner-address-${http_port}`;
// --- ENUMS and INTERFACES for P2P MESSAGING ---
var MessageType;
(function (MessageType) {
MessageType["CHAIN_REQUEST"] = "CHAIN_REQUEST";
MessageType["CHAIN_RESPONSE"] = "CHAIN_RESPONSE";
MessageType["NEW_BLOCK"] = "NEW_BLOCK";
MessageType["NEW_TRANSACTION"] = "NEW_TRANSACTION";
})(MessageType || (MessageType = {}));
// --- INITIALIZATION ---
const app = express();
app.use(cors());
app.use(express.json());
const blockchain = new Blockchain();
const ws = new WebSocket('ws://localhost:6001');
// --- P2P (WEBSOCKET) LOGIC ---
const broadcast = (message) => {
ws.send(JSON.stringify(message));
};
ws.on('open', () => {
console.log('Connected to the central relay server.');
broadcast({ type: MessageType.CHAIN_REQUEST });
});
ws.on('message', (message) => {
const data = JSON.parse(message);
console.log(`Received message: ${data.type}`);
switch (data.type) {
case MessageType.CHAIN_RESPONSE:
blockchain.replaceChain(data.payload);
break;
case MessageType.NEW_BLOCK:
const newBlock = data.payload;
const latestBlock = blockchain.getLatestBlock();
// A simple consensus rule: add block if its previousHash matches our latest block's hash
if (newBlock.previousHash === latestBlock.hash) {
blockchain.chain.push(newBlock);
// The new block contains transactions, so our pending pool should be cleared of those.
// A more robust implementation would filter them one-by-one.
blockchain.pendingTransactions = [];
console.log('New block added to the chain.');
}
else {
console.log('Received block is not the next in sequence. Requesting full chain.');
broadcast({ type: MessageType.CHAIN_REQUEST });
}
break;
case MessageType.NEW_TRANSACTION:
const txData = data.payload;
const tx = new Transaction(txData.fromAddress, txData.toAddress, txData.amount);
tx.timestamp = txData.timestamp;
tx.signature = txData.signature;
try {
blockchain.addTransaction(tx);
}
catch (error) {
console.log(`Ignoring invalid transaction: ${error.message}`);
}
break;
}
});
137 changes: 137 additions & 0 deletions rfc-1/dist/wallet-cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
// src/wallet-cli.ts
import elliptic from 'elliptic';
import SHA256 from 'crypto-js/sha256.js';
import { promises as fs } from 'fs';
import axios from 'axios';
const ec = new elliptic.ec('secp256k1');
const WALLET_FILE = 'wallet.json';
const MINER_API_URL = process.env.MINER_URL || 'http://localhost:3001';
// --- Helper Functions ---
function loadWallet() {
return __awaiter(this, void 0, void 0, function* () {
try {
const data = yield fs.readFile(WALLET_FILE, 'utf8');
return JSON.parse(data);
}
catch (e) {
console.error('❌ Error: No wallet found. Please create one with `npm run wallet -- create`');
process.exit(1);
}
});
}
function printHelp() {
console.log(`
SimpleCoin CLI Wallet
=====================
Usage: npm run wallet -- <command> [options]

Commands:
create Creates a new wallet and saves it to wallet.json.
balance Checks the balance of the wallet.
send <address> <amt> Sends <amt> of coins to <address>.
mine Tells the connected miner to mine a new block.
chain Displays the entire blockchain from the miner.

Example:
npm run wallet -- send 04abc...def 50
`);
}
// --- Main CLI Logic ---
function main() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h;
const args = process.argv.slice(2);
const command = args[0];
switch (command) {
case 'create': {
const key = ec.genKeyPair();
const publicKey = key.getPublic('hex');
const privateKey = key.getPrivate('hex');
const walletData = { privateKey, publicKey };
yield fs.writeFile(WALLET_FILE, JSON.stringify(walletData, null, 2));
console.log('✅ New wallet created and saved to wallet.json!');
console.log(` Public Key (Address): ${publicKey}`);
break;
}
case 'balance': {
const { publicKey } = yield loadWallet();
try {
const response = yield axios.get(`${MINER_API_URL}/balance/${publicKey}`);
console.log(`💰 Your balance is: ${response.data.balance} SC`);
}
catch (error) {
console.error('❌ Error fetching balance:', ((_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || error.message);
}
break;
}
case 'send': {
const toAddress = args[1];
const amount = parseInt(args[2]);
if (!toAddress || !amount) {
console.error('❌ Error: `send` command requires a recipient address and an amount.');
printHelp();
return;
}
const { privateKey, publicKey } = yield loadWallet();
const key = ec.keyFromPrivate(privateKey);
// Replicate the transaction hash calculation
const timestamp = Date.now();
const txHash = SHA256(publicKey + toAddress + amount + timestamp).toString();
const signature = key.sign(txHash, 'base64').toDER('hex');
try {
const response = yield axios.post(`${MINER_API_URL}/add-transaction`, {
fromAddress: publicKey,
toAddress,
amount,
timestamp,
signature
});
console.log(`✅ Transaction sent successfully! ${response.data.message}`);
}
catch (error) {
console.error('❌ Error sending transaction:', ((_d = (_c = error.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.message) || error.message);
}
break;
}
case 'mine': {
try {
console.log('⛏️ Telling miner to start mining...');
const response = yield axios.post(`${MINER_API_URL}/mine`);
console.log(`🎉 ${response.data.message}`);
console.log(' Block:', JSON.stringify(response.data.block, null, 2));
}
catch (error) {
console.error('❌ Error mining:', ((_f = (_e = error.response) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.message) || error.message);
}
break;
}
case 'chain': {
try {
const response = yield axios.get(`${MINER_API_URL}/blocks`);
console.log('⛓️ Current Blockchain:');
console.log(JSON.stringify(response.data, null, 2));
}
catch (error) {
console.error('❌ Error fetching chain:', ((_h = (_g = error.response) === null || _g === void 0 ? void 0 : _g.data) === null || _h === void 0 ? void 0 : _h.message) || error.message);
}
break;
}
default: {
printHelp();
break;
}
}
});
}
main().catch(err => {
console.error("An unexpected error occurred:", err);
});
1 change: 1 addition & 0 deletions rfc-1/node_modules/.bin/acorn

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rfc-1/node_modules/.bin/mime

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rfc-1/node_modules/.bin/nodemon

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rfc-1/node_modules/.bin/nodetouch

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading