-
Notifications
You must be signed in to change notification settings - Fork 0
/
blockchain.js
281 lines (215 loc) · 9.37 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
/**
* Blockchain Class
* The Blockchain class contain the basics functions to create your own private blockchain
* It uses libraries like `crypto-js` to create the hashes for each block and `bitcoinjs-message`
* to verify a message signature. The chain is stored in the array
* `this.chain = [];`. Of course each time you run the application the chain will be empty because and array
* isn't a persisten storage method.
*
*/
const SHA256 = require('crypto-js/sha256');
const BlockClass = require('./block.js');
const bitcoinMessage = require('bitcoinjs-message');
const { verify } = require('bitcoinjs-message');
const { address } = require('bitcoinjs-lib');
class Blockchain {
/**
* Constructor of the class, you will need to setup your chain array and the height
* of your chain (the length of your chain array).
* Also everytime you create a Blockchain class you will need to initialized the chain creating
* the Genesis Block.
* The methods in this class will always return a Promise to allow client applications or
* other backends to call asynchronous functions.
*/
constructor() {
this.chain = [];
this.height = -1;
this.initializeChain();
}
/**
* This method will check for the height of the chain and if there isn't a Genesis Block it will create it.
* You should use the `addBlock(block)` to create the Genesis Block
* Passing as a data `{data: 'Genesis Block'}`
*/
async initializeChain() {
if (this.height === -1) {
let block = new BlockClass.Block({data: 'Genesis Block' }); // create the Genesis Block
await this._addBlock(block);
}
}
/**
* Utility method that return a Promise that will resolve with the height of the chain
*/
getChainHeight() {
return new Promise((resolve, reject) => {
resolve(this.height);
});
}
/**
* _addBlock(block) will store a block in the chain
* @param {*} block
* The method will return a Promise that will resolve with the block added
* or reject if an error happen during the execution.
* You will need to check for the height to assign the `previousBlockHash`,
* assign the `timestamp` and the correct `height`...At the end you need to
* create the `block hash` and push the block into the chain array. Don't for get
* to update the `this.height`
* Note: the symbol `_` in the method name indicates in the javascript convention
* that this method is a private method.
*/
_addBlock(block) {
let self = this;
return new Promise(async (resolve, reject) => {
let height = self.chain.length;
block.previousBlockHash = self.chain[height - 1]
? self.chain[height - 1].hash
: null;
block.height = height;
block.time = new Date().getTime().toString().slice(0, -3);
block.hash = SHA256(JSON.stringify(block)).toString();
this.chain.push(block);
this.height = this.chain.length - 1;
this.validateChain();
let isValid = await this.validateChain(); //properly call ValidateChain() to check if the chain is valid
if (isValid) {
resolve(block);
} else {
reject('chain is not valid');
}
});
}
/**
* The requestMessageOwnershipVerification(address) method
* will allow you to request a message that you will use to
* sign it with your Bitcoin Wallet (Electrum or Bitcoin Core)
* This is the first step before submit your Block.
* The method return a Promise that will resolve with the message to be signed
* @param {*} address
*/
requestMessageOwnershipVerification(address) { // request message to be signed
return new Promise((resolve) => {
const message = `${address}:${new Date().getTime().toString().slice(0, -3)}:starRegistry`; //construct the message as explained, with the address + time + starRegistry
resolve(message);
});
}
/**
* The submitStar(address, message, signature, star) method
* will allow users to register a new Block with the star object
* into the chain. This method will resolve with the Block added or
* reject with an error.
* Algorithm steps:
* 1. Get the time from the message sent as a parameter example: `parseInt(message.split(':')[1])`
* 2. Get the current time: `let currentTime = parseInt(new Date().getTime().toString().slice(0, -3));`
* 3. Check if the time elapsed is less than 5 minutes
* 4. Veify the message with wallet address and signature: `bitcoinMessage.verify(message, address, signature)`
* 5. Create the block and add it to the chain
* 6. Resolve with the block added.
* @param {*} address
* @param {*} message
* @param {*} signature
* @param {*} star
*/
submitStar(address, message, signature, star) {
let self = this;
return new Promise(async (resolve, reject) => {
let messageTime = parseInt(message.split(':')[1]);
let currentTime = parseInt(new Date().getTime().toString().slice(0, -3));
if (currentTime - messageTime > 300) { // check if the time elapsed is greater than 5 minutes
resolve('Time out!'); //
}
else { // if the time elapsed is less than 5 minutes
if (bitcoinMessage.verify(message, address, signature)) {
const starData = { owner: address, star: star }; // create the star object
let blck = new BlockClass.Block(starData); // create the block
resolve(await self._addBlock(blck)); // add the block to the chain
}
else { // if the message is not verified
resolve('message verification failed');
}
}
});
}
/**
* This method will return a Promise that will resolve with the Block
* with the hash passed as a parameter.
* Search on the chain array for the block that has the hash.
* @param {*} hash
*/
getBlockByHash(hash) {
let self = this;
return new Promise((resolve, reject) => {
let block = self.chain.filter(p => p.hash == hash)[0];
if (block) {
resolve(block);
}
else {
resolve('hash block not found');
}
});
}
/**
* This method will return a Promise that will resolve with the Block object
* with the height equal to the parameter `height`
* @param {*} height
*/
getBlockByHeight(height) {
let self = this;
return new Promise((resolve, reject) => {
let block = self.chain.filter(p => p.height === height)[0];
if(block) {
resolve(block);
} else {
resolve(null);
}
}); // end of Promise
} // end of getBlockByHeight
/**
* This method will return a Promise that will resolve with an array of Stars objects existing in the chain
* and are belongs to the owner with the wallet address passed as parameter.
* Remember the star should be returned decoded.
* @param {*} address
*/
getStarsByWalletAddress(address) {
let self = this;
let stars = [];
return new Promise((resolve, reject) => {
self.chain.forEach(async (b) => {
let data = await b.getBData();
console.log(data);
if (data.owner === address) {
stars.push(data);
}
}); // end of forEach
resolve(stars);
}); // end of Promise
} // end of getStarsByWalletAddress
/**
* This method will return a Promise that will resolve with the list of errors when validating the chain.
* Steps to validate:
* 1. You should validate each block using `validateBlock`
* 2. Each Block should check the with the previousBlockHash
*/
validateChain() {
let self = this;
let errorLog = [];
return new Promise((resolve, reject) => {
let previousBlockHash = null;
self.chain.forEach(async (b) => {
if (await b.validate()) {
if (b.previousBlockHash === previousBlockHash) {
previousBlockHash = b.hash;
resolve('chain is valid');
}
else {
errorLog.push(Error('the chain is broken'));
}
}
else {
errorLog.push(await b.validate());
resolve(errorLog);
}
}) // end of forEach
}); // end of Promise
}
} // end of validateChain
module.exports.Blockchain = Blockchain;