-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblockchain.js
371 lines (318 loc) · 10.5 KB
/
blockchain.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
"use strict";
const crypto = require('crypto');
// Network message constants
const MISSING_BLOCK = "MISSING_BLOCK";
const POST_TRANSACTION = "POST_TRANSACTION";
const PROOF_FOUND = "PROOF_FOUND";
const START_MINING = "START_MINING";
// Constants for mining
const NUM_ROUNDS_MINING = 2000;
// Constants related to proof-of-work target
const POW_BASE_TARGET = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
const POW_LEADING_ZEROES = 15;
// Constants for mining rewards and default transaction fees
const COINBASE_AMT_ALLOWED = 25;
const DEFAULT_TX_FEE = 1;
// If a block is 6 blocks older than the current block, it is considered
// confirmed, for no better reason than that is what Bitcoin does.
// Note that the genesis block is always considered to be confirmed.
const CONFIRMED_DEPTH = 6;
/**
* The Blockchain class tracks configuration information and settings for the
* blockchain, as well as some utility methods to allow for easy extensibility.
* Note that the genesis block is the only direct reference to a block, since
* different clients may have different blocks.
*/
module.exports = class Blockchain {
static get MISSING_BLOCK() { return MISSING_BLOCK; }
static get POST_TRANSACTION() { return POST_TRANSACTION; }
static get PROOF_FOUND() { return PROOF_FOUND; }
static get START_MINING() { return START_MINING; }
static get NUM_ROUNDS_MINING() { return NUM_ROUNDS_MINING; }
// Configurable properties, with static getters for convenience.
static get POW_TARGET() {
let bc = Blockchain.getInstance();
return bc.powTarget;
}
static get COINBASE_AMT_ALLOWED() {
let bc = Blockchain.getInstance();
return bc.coinbaseReward;
}
static get DEFAULT_TX_FEE() {
let bc = Blockchain.getInstance();
return bc.defaultTxFee;
}
static get CONFIRMED_DEPTH() {
let bc = Blockchain.getInstance();
return bc.confirmedDepth;
}
/**
* Produces a new genesis block, giving the specified clients the amount of
* starting gold specified in the initialBalances field of the Blockchain
* instance. This function also sets the genesis block for every client in
* the clients field of the Blockchain instance.
*
* @returns {Block} - The genesis block.
*/
static makeGenesis() {
let g = this.makeBlock();
let bc = Blockchain.getInstance();
// Initializing starting balances in the genesis block.
g.balances = new Map(bc.initialBalances);
for (let client of bc.clients) {
client.setGenesisBlock(g);
}
return g;
}
/**
* Converts a string representation of a block to a new Block instance.
*
* @param {Object} o - An object representing a block, but not necessarily an instance of Block.
*
* @returns {Block}
*/
static deserializeBlock(o) {
if (o instanceof this.instance.blockClass) {
return o;
}
let b = new this.instance.blockClass();
b.chainLength = parseInt(o.chainLength, 10);
b.timestamp = o.timestamp;
b.randSalt = crypto.randomBytes(16).toString('hex');
if (b.isGenesisBlock()) {
// Balances need to be recreated and restored in a map.
o.balances.forEach(([clientID,amount]) => {
b.balances.set(clientID, amount);
});
} else {
b.prevBlockHash = o.prevBlockHash;
// b.proof = o.proof;
b.sudoku_puzzle = o.sudoku_puzzle;
b.sudoku_result = o.sudoku_result;
b.blockTime = o.blockTime;
b.prevBlockTime = o.prevBlockTime;
b.moves_made = o.moves_made;
b.rewardAddr = o.rewardAddr;
// Likewise, transactions need to be recreated and restored in a map.
b.transactions = new Map();
if (o.transactions) o.transactions.forEach(([txID,txJson]) => {
let tx = this.makeTransaction(txJson);
b.transactions.set(txID, tx);
});
}
return b;
}
/**
* @param {...any} args - Arguments for the Block constructor.
*
* @returns {Block}
*/
static makeBlock(...args) {
let bc = Blockchain.getInstance();
return bc.makeBlock(...args);
}
/**
* @param {...any} args - Arguments for the Transaction constructor.
* @returns {Transaction}
*/
static makeTransaction(...args) {
let bc = Blockchain.getInstance();
return bc.makeTransaction(...args);
}
/**
* Get the instance of the blockchain configuration class.
*
* @returns {Blockchain}
*/
static getInstance() {
if (!this.instance) {
throw new Error("The blockchain has not been initialized.");
}
return this.instance;
}
/**
* Creates the new instance of the blockchain configuration, giving the
* clients the amount of starting gold specified in the clients array.
* This will also create the genesis block, but will not start mining.
*
* @param {Object} cfg - Settings for the blockchain.
* @param {Class} cfg.blockClass - Implementation of the Block class.
* @param {Class} cfg.transactionClass - Implementation of the Transaction class.
* @param {Array} [cfg.clients] - An array of client/miner configurations.
* @param {number} [cfg.powLeadingZeroes] - Number of leading zeroes required for a valid proof-of-work.
* @param {number} [cfg.coinbaseAmount] - Amount of gold awarded to a miner for creating a block.
* @param {number} [cfg.defaultTxFee] - Amount of gold awarded to a miner for accepting a transaction,
* if not overridden by the client.
* @param {number} [cfg.confirmedDepth] - Number of blocks required after a block before it is
* considered confirmed.
*
* @returns {Blockchain} - The blockchain configuration instance.
*/
static createInstance(cfg) {
this.instance = new Blockchain(cfg);
this.instance.genesis = this.makeGenesis();
return this.instance;
}
/**
* Constructor for the Blockchain configuration. This constructor should not
* be called outside of the class; nor should it be called more than once.
*
* @constructor
*/
constructor({
blockClass,
transactionClass,
clientClass,
minerClass,
powLeadingZeroes = POW_LEADING_ZEROES,
coinbaseReward = COINBASE_AMT_ALLOWED,
defaultTxFee = DEFAULT_TX_FEE,
confirmedDepth = CONFIRMED_DEPTH,
clients = [],
net,
}) {
if (this.constructor.instance) {
throw new Error("The blockchain has already been initialized.");
}
// Storing details on classes.
if (blockClass) {
this.blockClass = blockClass;
} else {
this.blockClass = require('./block');
}
if (transactionClass) {
this.transactionClass = transactionClass;
} else {
this.transactionClass = require('./transaction');
}
if (clientClass) {
this.clientClass = clientClass;
} else {
this.clientClass = require('./client');
}
if (minerClass) {
this.minerClass = minerClass;
} else {
this.minerClass = require('./miner');
}
this.clients = [];
this.miners = [];
this.clientAddressMap = new Map();
this.clientNameMap = new Map();
this.net = net;
this.powLeadingZeroes = powLeadingZeroes;
this.coinbaseReward = coinbaseReward;
this.defaultTxFee = defaultTxFee;
this.confirmedDepth = confirmedDepth;
this.powTarget = POW_BASE_TARGET >> BigInt(powLeadingZeroes);
this.initialBalances = new Map();
clients.forEach((clientCfg) => {
console.log(`Adding client ${clientCfg.name}`);
let client;
if (clientCfg.mining) {
client = new this.minerClass({
name: clientCfg.name,
net: this.net,
miningRounds: clientCfg.miningRounds,
});
// Miners are stored as both miners and clients.
this.miners.push(client);
} else {
client = new this.clientClass({
name: clientCfg.name,
net: this.net,
});
}
this.clientAddressMap.set(client.address, client);
if (client.name) this.clientNameMap.set(client.name, client);
this.clients.push(client);
this.net.register(client);
this.initialBalances.set(client.address, clientCfg.amount);
});
}
/**
* Prints out the balances from one client's view of the blockchain. A
* specific client may be named; if no client name is specified, then the
* first client in the clients array is used.
*
* @param {string} [name] - The name of the client whose view
* of the blockchain will be used.
*/
showBalances(name) {
let client = name ? this.clientNameMap.get(name) : this.clients[0];
if (!client) throw new Error("No client found.");
client.showAllBalances();
}
/**
* Tells all miners to start mining new blocks.
*
* @param {number} [ms] - Delay in milliseconds before the blockchain
* terminates. If omitted, the program will run indefinitely.
* @param {Function} [f] - Callback function that will be executed when the
*/
start(ms, f) {
this.miners.forEach((miner) => {
miner.initialize();
});
if (ms) {
setTimeout(() => {
if (f) f();
process.exit(0);
}, ms);
}
}
/**
* @param {...any} args - Parameters for the Block constructor.
*
* @returns {Block}
*/
makeBlock(...args) {
return new this.blockClass(...args);
}
/**
* @param {*} o - Either an object with the transaction details, o an
* instance of the Transaction class.
*
* @returns {Transaction}
*/
makeTransaction(o) {
if (o instanceof this.transactionClass) {
return o;
} else {
return new this.transactionClass(o);
}
}
/**
* Looks up clients by name, returning a list of the matching clients.
*
* @param {...string} names - Names of all clients to return.
*
* @returns {Array} - An array of clients
*/
getClients(...names) {
let clients = [];
names.forEach((clientName) => {
clients.push(this.clientNameMap.get(clientName));
});
return clients;
}
register(...clients) {
clients.forEach((client) => {
this.clientAddressMap.set(client.address, client);
if (client.name) this.clientNameMap.set(client.name, client);
// Add client to the list of clients and (if a miner) the list of miners.
this.clients.push(client);
if (client instanceof this.minerClass) this.miners.push(client);
// Set the "network" connection for the client.
client.net = this.net;
this.net.register(client);
});
}
getClientName(address) {
if (!this.clientAddressMap.has(address)) {
return;
}
let client = this.clientAddressMap.get(address);
return client.name;
}
};