-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
397 lines (353 loc) · 16.1 KB
/
index.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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
const { exec } = require('child_process');
const _RPC = require('./src/rpc');
const express=require('express');
const NET = require('./src/net.js');
const nodemailer = require('nodemailer');
const os = require('os')
// Required to unzip files
var yauzl = require("yauzl");
require('dotenv').config()
//Adding in the express code for the backend and communication for each daemon
// Create an instance of the express application
const app=express();
// Specify a port number for the server
const port=5000;
// Start the server and listen to the port
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
app.get('/stats', async function(request, response) {
//We will require an API key on all nodes so that these responses are not public
if(request.header.Authorization == process.env.APIKey){
//Get the blockcount of the node
let responseJson = {}
responseJson.blockCount = await cRPC.call('getblockcount');
response.json(responseJson)
}
});
//Interacting and communicating with the underlying daemon
//set hostname
const hostname = os.hostname();
// Prep the RPC daemon
const cRPC = new _RPC(process.env.WALLET_USER, process.env.WALLET_PASSWORD, '127.0.0.1', process.env.WALLET_PORT);
// Just an auto-handled RPC lock: don't touch.
let fLocked = false;
// MUST-HAVE: a minimum "healthy" block height peers MUST have, or get booted
const minBlockHeight = 4230608;
// Every 2.5s, scan for "bad" peers and boot 'em
setInterval(async () => {
if (!fLocked) {
const arrPeers = await cRPC.call('getpeerinfo');
for (const cPeer of arrPeers) {
if (cPeer.startingheight <= minBlockHeight) {
// Disconnect the lazy (possibly forked) bastard
await cRPC.call('disconnectnode', cPeer.addr);
console.log('Disconnected peer id: ' + cPeer.id + ' address: '+ cPeer.addr + ' at block ' + cPeer.startingheight.toLocaleString('en-gb'));
}
}
}
}, 2500);
// Every 1m, create a "good" addnode list, and optionally invalidate some bad blocks here to help with "steering" your chain tip
setInterval(async () => {
if (!fLocked) {
// Optional: uncomment and edit to invalidate known-bad blocks, helps prevent forks mid-sync
// await cRPC.call('invalidateblock', '0000000000000442c69a293eb0713ceb04b2545a443affca4c77d7c3ac866cde');
const arrPeers = (await cRPC.call('getpeerinfo')).filter(a => a.startingheight > minBlockHeight);
console.log('=== Healthy-ish Addnode List for users and alternative nodes ===');
for (const cPeer of arrPeers) {
console.log('addnode ' + cPeer.addr + ' add');
console.log('addnode ' + cPeer.addr + ' onetry');
}
}
}, 60000);
async function sendEmailNotification(error){
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: process.env.SMTP_SEC, // Use `true` for port 465, `false` for all other ports
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
// send mail with defined transport object
const info = await transporter.sendMail({
from: process.env.SMTP_USER, // sender address
to: process.env.SMTP_RECEIVER, // list of receivers
// TODO: create a node identifier either by ip or hostname or both
subject: "NODE MESSAGE " + process.env.NODE_NAME, // Subject line
text: "NODE ERROR \n" + error, // plain text body
html: "<b>NODE ERROR</b><br>" + error, // html body
});
console.log("Message sent: %s", info.messageId);
info.catch(console.error);
}
async function checkDaemonVersion(){
// First run an RPC call to get the version we are using currently
let localDaemonVersion = await(cRPC.call('getinfo'))
// Trezor check
let zkbitcoinDaemonVersion = JSON.parse(await NET.get('https://zkbitcoin.com' + '/api/'))
if(localDaemonVersion.version != zkbitcoinDaemonVersion.backend.version){
console.log("Possibly a bad daemon version Local doesn't match remote: zkbitcoin")
// If any of the explorers don't match trigger a github check to figure out the newest github version
let githubReleaseInfo = JSON.parse(await NET.get('https://api.github.com/repos/PIVX-Project/PIVX/releases/latest'))
// The tag name is almost never a string
// Remove the version from the tag
let githubDaemonVersion = (githubReleaseInfo.name).replace('v','')
// Modify the localDaemon hardcoded version to match the github shortened version
if((localDaemonVersion.version.toString()).replace(/0/g,'') != (githubDaemonVersion.toString()).replaceAll('.','')){
console.log("Local Daemon possibly not up to date")
}else{
console.log("Local Daemon and github remote daemon version up to date")
}
}
}
/**
* Takes two params and returns the block height and the best block hash
* @param {string} domain
* @param {string} type
*/
async function checkExplorer(domain, type, block){
// check if we are looking for a block or just over all info about the block on an explorer
if(typeof block == 'undefined'){
let bestBlockData = {}
if(type == "TREZOR"){
let res = JSON.parse(await NET.get(domain + '/api/',));
bestBlockData.newestBlock = res.backend.blocks
bestBlockData.newestBlockHash = res.backend.bestBlockHash
}else if(type == "CRYPTOID"){
bestBlockData.newestBlock = parseInt(await NET.get(domain + '/api.dws?q=getblockcount'))
// wait 5 seconds to abide by their recommendation on how often we should call
await setTimeout(function(){},5000)
let preStriping = await NET.get(domain + '/api.dws?q=getblockhash&height=' + bestBlockData.newestBlock)
bestBlockData.newestBlockHash = preStriping.replace(/['"]+/g, "")
}else if(type == "CRYPTOSCOPE"){
bestBlockData.newestBlock = JSON.parse(await NET.get(domain + '/api/getblockcount/')).blockcount
// wait 5 seconds to abide by there recommendation on how often we should call
await setTimeout(function(){},5000)
let preStriping = await NET.get(domain + '/api/getblockhash/?index=' + bestBlockData.newestBlock)
bestBlockData.newestBlockHash = preStriping.replace(/['"]+/g, "")
}
return bestBlockData
}else{
// use the blockid to get the hash value
let bestBlockData = {}
if(type == "TREZOR"){
let res = JSON.parse(await NET.get(domain + '/api/v2/block-index/' + block,));
bestBlockData.newestBlockHash = res.blockHash
}else if(type == "CRYPTOID"){
let preStriping = await NET.get(domain + '/api.dws?q=getblockhash&height=' + block)
bestBlockData.newestBlockHash = preStriping.replace(/['"]+/g, "")
}else if(type == "CRYPTOSCOPE"){
let res = JSON.parse(await NET.get(domain + '/api/getblockhash/?index=' + block))
bestBlockData.newestBlockHash = res.hash
}
return bestBlockData
}
}
async function compareToExplorer(){
// Make another array that holds the explorer information
let explorerUrlData = [
{
variableName:"ChainzData",
url:"https://chainz.cryptoid.info/pivx",
type:"CRYPTOID",
},
{
variableName:"zkbitcoinData",
url:"https://zkbitcoin.com",
type:"TREZOR",
},
{
variableName:"cryptoscope",
url:"https://pivx.cryptoscope.io",
type:"CRYPTOSCOPE",
},
]
// Grab data from explorers
let ChainzData = await checkExplorer('https://chainz.cryptoid.info/pivx', "CRYPTOID")
let zkbitcoinData = await checkExplorer('https://zkbitcoin.com', "TREZOR")
let cryptoscope = await checkExplorer('https://pivx.cryptoscope.io', "CRYPTOSCOPE")
let networkFork = false;
// Name the explorers to make it easier in the future
ChainzData.name = "ChainzData"
zkbitcoinData.name = "zkbitcoinData"
cryptoscope.name = 'cryptoscope'
let allExplorers = [ChainzData,zkbitcoinData,cryptoscope]
// Creates a new object, finds the matches and lists how many match together
let result = allExplorers.reduce( (acc, o) => (acc[o.newestBlock] = (acc[o.newestBlock] || 0)+1, acc), {} );
// Find how many is the most matches
const max = Math.max.apply(null, Object.values(result));
// Find index of that how many is the most matches
var index = Math.max.apply(null, Object.keys(result));
// Figure out if there are multiple matching large instance if so we have a serious fork on the network
if(!Array.isArray(index)){
console.log("The block that matches the most explorers is: " + index + " and is matched by " + max + " explorers")
}else{
networkFork = true;
console.log("Big ass fork")
}
// Check what our node says compared to the network
let localNodeBlockcount = await cRPC.call('getblockcount');
console.log(localNodeBlockcount)
// check if there are large network-wide issues
if(!networkFork){
if(localNodeBlockcount == index){
console.log("Local node block count matches remote block count")
}else{
console.log("Local node block count does not match remote node block count")
// check if the localNode is less then the index (might be slower then the explorer)
if(localNodeBlockcount < index){
console.log("Local node has less blocks then remotes")
// figure out which explorers have more then the localNodeBlockcount
for(let i=0; i<allExplorers.length;i++){
// grab localNodeBlockHash
let localNodeBlockHash = await cRPC.call('getblockhash', localNodeBlockcount);
// check if the explorer's block is newer then localNodeBlock
if(allExplorers[i].newestBlock >= localNodeBlockcount){
// send a request for the blockhash we have
let urlCallInfo = explorerUrlData.find((element) => element.variableName == allExplorers[i].name)
let blockhashreturn = await checkExplorer(urlCallInfo.url, urlCallInfo.type, localNodeBlockcount)
// check if the hash agrees with our localNode's hash
console.log(localNodeBlockHash)
if(localNodeBlockHash == blockhashreturn.newestBlockHash){
console.log("Hash matches")
}else{
console.log("Hash does not match")
}
}
}
}else if(localNodeBlockcount > index){
console.log("Local node Has more blocks then remote")
// Possibly a slow explorer
}else{
console.log("Unhandled exception:0001")
}
}
}
//TODO: check if the bootstrap setting is true and try and fix it by bootstrapping and unzipping
//bootstrap();
}
async function bootstrap(){
if(process.env.BOOTSTRAP == 't' || process.env.BOOTSTRAP_FORK == 't'){
// Download from toolbox.pivx.org
let http = require('http');
let fs = require('fs');
let download = function(url, dest, cb) {
let file = fs.createWriteStream(dest);
let request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() is async, call cb after close completes.
});
}).on('error', function(err) { // Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
if (cb) cb(err.message);
});
};
// Shutdown wallet when done downloading
try {await cRPC.call('setnetworkactive', false);} catch(e){}
try {await cRPC.call('stop');} catch(e){}
// remove data folders: blocks, chainstate, sporks, zerocoin, and files banlist.dat, peers.dat
fs.unlink('banlist.dat', (err) => {
if (err) throw err;
console.log('banlist.txt was deleted');
});
fs.unlink('peers.dat', (err) => {
if (err) throw err;
console.log('peers.txt was deleted');
});
fs.rmdir('blocks', (err) => {
if (err) throw err;
console.log('blocks was deleted');
});
fs.rmdir('chainstate', (err) => {
if (err) throw err;
console.log('blocks was deleted');
});
fs.rmdir('sporks', (err) => {
if (err) throw err;
console.log('blocks was deleted');
});
fs.rmdir('zerocoin', (err) => {
if (err) throw err;
console.log('blocks was deleted');
});
// unzip the bootstrap
yauzl.open("PIVXsnapshotLatest.zip", {lazyEntries: true}, function(err, zipFile) {
if (err) throw err;
zipFile.readEntry();
zipFile.on("entry", function(entry) {
if (/\/$/.test(entry.fileName)) {
// Directory file names end with '/'.
// Note that entries for directories themselves are optional.
// An entry's fileName implicitly requires its parent directories to exist.
zipFile.readEntry();
} else {
// file entry
zipFile.openReadStream(entry, function(err, readStream) {
if (err) throw err;
readStream.on("end", function() {
zipFile.readEntry();
});
readStream.pipe(somewhere);
});
}
});
});
// remove the bootstrap
fs.unlink('PIVXsnapshotLatest.zip', (err) => {
if (err) throw err;
console.log('PIVXsnapshotLatest.zip was deleted');
});
// start the wallet
setTimeout(async () => {
let directoryConcat = process.env.DAEMON_DIRECTORY + "/pivxd -daemon"
console.log('Starting daemon...');
exec(directoryConcat, (error, stdout, stderr) => {
if (error) console.error(`exec error: ${error}`);
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
fLocked = false;
});
}, 60000 * 2);
}
}
console.log("Starting PIVX Node Checker on " + hostname + "...")
// We need to check the Daemon version to make sure that it is the correct version that is being used on the network/github etc
// And give a warning if it isn't
// loop this check every two minutes (a block is on avg a minute on pivx)
async function checkAgainstExternalSources(){
checkDaemonVersion()
compareToExplorer()
}
/**
* This will restart the daemon
*/
async function restart() {
fLocked = true;
console.log('Restart time! - Disconnecting peers...');
try {await cRPC.call('setnetworkactive', false);} catch(e){}
setTimeout(async () => {
console.log('Stopping daemon...');
try {await cRPC.call('stop');} catch(e){}
setTimeout(async () => {
let directoryConcat = process.env.DAEMON_DIRECTORY + "/pivxd -daemon"
console.log('Starting daemon...');
exec(directoryConcat, (error, stdout, stderr) => {
if (error) console.error(`exec error: ${error}`);
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
fLocked = false;
});
}, 60000 * 2);
}, 15000);
}
// Loops------
// check against external sources every five minutes for wallet updates and block comparison
setInterval(checkAgainstExternalSources, 60000 * 5);
// Optional in case of wallet memleaks: Restart the daemon every 15m
if(process.env.RESTART_WALLET == "t"){
console.log("restarting wallet every 15 minutes")
setInterval(restart, 60000 * 15);
}