Skip to content

Commit 1de48ef

Browse files
committed
adds blockchain implementation
1 parent 3b53377 commit 1de48ef

4 files changed

Lines changed: 268 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Remember that each data has its own trade-offs. And you need to pay attention mo
6969
* `A` [Disjoint Set](src/data-structures/disjoint-set) - a union–find data structure or merge–find set
7070
* `A` [Bloom Filter](src/data-structures/bloom-filter)
7171
* `A` [LRU Cache](src/data-structures/lru-cache/) - Least Recently Used (LRU) cache
72+
* `A` [Blockchain](src/data-structures/blockchain/) - Blockchain
7273

7374
## Algorithms
7475

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import Block from './BlockchainBlock';
2+
import BlockchainTransaction from './BlockchainTransaction';
3+
4+
export default class Blockchain {
5+
/**
6+
* @param {number} [difficulty=2] The difficulty level for mining new blocks
7+
* Higher values require more computational work
8+
*/
9+
constructor(difficulty = 2) {
10+
this.difficulty = difficulty;
11+
/**
12+
* @type {Array<Block>}
13+
*/
14+
this.chain = [];
15+
/**
16+
* @type {Array<BlockchainTransaction>}
17+
*/
18+
this.pendingTransactions = [];
19+
/**
20+
* @type {number}
21+
*/
22+
this.minerReward = 10;
23+
/**
24+
* @type {Map<string, number>}
25+
*/
26+
this.balances = new Map();
27+
/**
28+
* @type {Map<string, Array<Object>>}
29+
*/
30+
this.transactionsByAddress = new Map();
31+
this.createGenesisBlock();
32+
}
33+
34+
/**
35+
* Creates the first block in the chain.
36+
* @private
37+
*/
38+
createGenesisBlock() {
39+
const genesisBlock = new Block({
40+
index: 0,
41+
data: 'Genesis Block',
42+
previousHash: '0',
43+
});
44+
genesisBlock.mineBlock(this.difficulty);
45+
this.chain.push(genesisBlock);
46+
}
47+
48+
/**
49+
* @returns {Block} The last block in the chain
50+
*/
51+
getLatestBlock() {
52+
return this.chain[this.chain.length - 1];
53+
}
54+
55+
/**
56+
* @param {string} address The address whose balance should be adjusted
57+
* @param {number} amount The amount to add to the current balance
58+
*/
59+
updateBalance(address, amount) {
60+
this.balances.set(address, (this.balances.get(address) || 0) + amount);
61+
}
62+
63+
/**
64+
* @param {number} blockIndex The index of the block that stores the transaction
65+
* @param {BlockchainTransaction} transaction The transaction to index
66+
*/
67+
indexTransaction(blockIndex, transaction) {
68+
const addresses = new Set([transaction.from, transaction.to]);
69+
for (const address of addresses) {
70+
const addressTransactions = this.transactionsByAddress.get(address) || [];
71+
addressTransactions.push({
72+
block: blockIndex,
73+
transaction,
74+
});
75+
this.transactionsByAddress.set(address, addressTransactions);
76+
}
77+
}
78+
79+
/**
80+
* @param {BlockchainTransaction} transaction The transaction object to add
81+
* @returns {boolean} True if the transaction was added successfully
82+
*/
83+
addTransaction(transaction) {
84+
// Basic validation: in production, use more robust checks
85+
if (!transaction.from || !transaction.to || !transaction.amount) {
86+
return false;
87+
}
88+
89+
if (transaction.amount <= 0) {
90+
// Invalid transaction: amount must be positive
91+
return false;
92+
}
93+
94+
this.pendingTransactions.push(transaction);
95+
this.updateBalance(transaction.from, -transaction.amount);
96+
this.updateBalance(transaction.to, transaction.amount);
97+
98+
return true;
99+
}
100+
101+
/**
102+
* Mines pending transactions into a new block and rewards the miner.
103+
* This method creates a new block, mines it according to the difficulty level,
104+
* and adds it to the blockchain.
105+
*
106+
* @param {string} minerAddress - The address of the miner who will receive the reward
107+
* @returns {Block|null} The newly mined block, or null if no pending transactions exist
108+
*/
109+
minePendingTransactions(minerAddress) {
110+
// Add miner reward transaction
111+
const transaction = new BlockchainTransaction({
112+
from: 'System',
113+
to: minerAddress,
114+
amount: this.minerReward,
115+
description: 'Mining Reward',
116+
});
117+
this.pendingTransactions.push(transaction);
118+
this.updateBalance(transaction.from, -transaction.amount);
119+
this.updateBalance(transaction.to, transaction.amount);
120+
121+
// Create new block with pending transactions
122+
const newBlock = new Block({
123+
index: this.chain.length,
124+
data: this.pendingTransactions,
125+
previousHash: this.getLatestBlock().hash,
126+
});
127+
128+
// Mine the block
129+
newBlock.mineBlock(this.difficulty);
130+
131+
// Add to chain
132+
this.chain.push(newBlock);
133+
134+
for (const transaction of newBlock.data) {
135+
this.indexTransaction(newBlock.index, transaction);
136+
}
137+
138+
// Clear pending transactions
139+
this.pendingTransactions = [];
140+
141+
return newBlock;
142+
}
143+
144+
/**
145+
* Validates the entire blockchain by checking:
146+
* 1. Each block's hash is correctly calculated
147+
* 2. Each block's previous hash matches the previous block's hash
148+
* 3. No blocks have been tampered with
149+
*
150+
* @returns {boolean} True if the blockchain is valid, false otherwise
151+
*/
152+
isChainValid() {
153+
if (this.chain.length <= 1) {
154+
// An empty chain or a chain with only the genesis block is considered valid
155+
return true;
156+
}
157+
158+
// Check each block starting from the second block (index 1)
159+
for (let i = 1; i < this.chain.length; i++) {
160+
const currentBlock = this.chain[i];
161+
const previousBlock = this.chain[i - 1];
162+
163+
// Verify current block's hash is correct
164+
if (currentBlock.hash !== currentBlock.calculateHash()) {
165+
// The block has been tampered with
166+
return false;
167+
}
168+
169+
// Verify link to previous block
170+
if (currentBlock.previousHash !== previousBlock.hash) {
171+
// The link to the previous block is invalid
172+
return false;
173+
}
174+
}
175+
176+
return true;
177+
}
178+
179+
/**
180+
* @param {string} address The address to check balance for
181+
* @returns {number} The total balance of the address
182+
*/
183+
getBalance(address) {
184+
return this.balances.get(address) || 0;
185+
}
186+
187+
/**
188+
* @param {string} address The address to find transactions for
189+
* @returns {Array<Object>} An array of transaction objects involving the address
190+
*/
191+
getTransactionsForAddress(address) {
192+
return (this.transactionsByAddress.get(address) || []).slice();
193+
}
194+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import crypto from 'crypto';
2+
3+
export default class Block {
4+
/**
5+
* @param {Object} blockParams - The block parameters
6+
* @param {number} blockParams.index - The position of the block in the blockchain
7+
* @param {string} [blockParams.timestamp] - The creation time of the block in ISO format
8+
* @param {Array|String} blockParams.data - The transaction data stored in this block
9+
* @param {string} blockParams.previousHash - The hash of the previous block in the chain
10+
* @param {number} [blockParams.nonce=0] - The number used once for proof of work calculations
11+
*/
12+
constructor({
13+
index,
14+
timestamp = new Date().toISOString(),
15+
data,
16+
previousHash,
17+
nonce = 0,
18+
}) {
19+
this.index = index;
20+
this.timestamp = timestamp;
21+
this.data = data;
22+
this.previousHash = previousHash;
23+
this.nonce = nonce;
24+
this.hash = this.calculateHash();
25+
}
26+
27+
/**
28+
* @private
29+
* @returns {string} The hexadecimal representation of the block's hash
30+
*/
31+
calculateHash() {
32+
const blockData = JSON.stringify({
33+
index: this.index,
34+
timestamp: this.timestamp,
35+
data: this.data,
36+
previousHash: this.previousHash,
37+
nonce: this.nonce,
38+
});
39+
40+
return crypto.createHash('sha256').update(blockData).digest('hex');
41+
}
42+
43+
/**
44+
* @param {number} difficulty - The number of leading zeros required in the hash
45+
*/
46+
mineBlock(difficulty) {
47+
const target = '0'.repeat(difficulty);
48+
while (this.hash.substring(0, difficulty) !== target) {
49+
this.nonce += 1;
50+
this.hash = this.calculateHash();
51+
}
52+
}
53+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export default class BlockchainTransaction {
2+
/**
3+
* @param {Object} transactionParams - The transaction parameters
4+
* @param {string} transactionParams.from - The sender's address
5+
* @param {string} transactionParams.to - The recipient's address
6+
* @param {number} transactionParams.amount - The transaction amount
7+
* @param {string} [transactionParams.description=''] - Optional description of the transaction
8+
*/
9+
constructor({
10+
from,
11+
to,
12+
amount,
13+
description = '',
14+
}) {
15+
this.from = from;
16+
this.to = to;
17+
this.amount = amount;
18+
this.description = description;
19+
}
20+
}

0 commit comments

Comments
 (0)