Blockchain explorer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

290 lines
6.7 KiB

var http = require('http');
const fs = require('fs-ext');
const moment = require('moment');
var models = require('../models');
var rpcConfig = require('../config/config')['rpc'];
var HeightOffset = require('../config/config')['syncHeightOffset'] || 0;
const {username, password, hostname, port} = rpcConfig;
const keepAliveAgent = new http.Agent({ keepAlive: true });
let sync_sql = '',
coolstrs = [],
starttime = 0;
function MakeRPCRequest(postData) {
return new Promise(function(resolve, reject) {
var post_options = {
host: hostname,
port: port,
auth: `${username}:${password}`,
path: '/',
method: 'POST',
agent: keepAliveAgent,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
var post_req = http.request(post_options, function(res) {
res.setEncoding("utf8");
let body = "";
res.on("data", data => {
body += data;
});
res.on("end", () => {
resolve(body);
});
});
post_req.on('error', function(err) {
if (err.code == 'ECONNREFUSED') {
console.log('\x1b[36m%s\x1b[0m', "Couldn't make request to wallet")
}
reject(err);
});
post_req.write(postData);
post_req.end();
});
}
async function saveTransaction(txid, blockHeight) {
const res_tx = await MakeRPCRequest(JSON.stringify({
method: 'getrawtransaction',
params: [txid, 1],
id: 1
}));
const tx = JSON.parse(res_tx)['result'];
if (tx === null) {
await models.Failure.create({
msg: `${txid} fetching failed`,
});
return;
}
sync_sql += `
INSERT INTO Transactions (
txid,
BlockHeight
)
VALUES (
"${txid}",
${blockHeight}
);
SET @txid = LAST_INSERT_ID();
`;
// Loop over vout's
for (var i = 0; i < tx.vout.length; i++) {
const vout = tx.vout[i];
sync_sql += `
INSERT INTO Vouts (n, value)
VALUES ("${vout.n}", "${vout.value}");
SET @voutid= LAST_INSERT_ID();
`;
// Loop over addresses in vout
for (var y = 0; y < vout.scriptPubKey.addresses.length; y++) {
const address = vout.scriptPubKey.addresses[y];
sync_sql += `
INSERT IGNORE INTO Addresses (address) VALUES ("${address}");
SET @addrid = (
SELECT IF(
ROW_COUNT() > 0,
LAST_INSERT_ID(),
(
SELECT id
FROM Addresses
WHERE address='${address}'
)
)
);
`;
sync_sql += `
INSERT INTO AddressVouts (AddressId, VoutId)
VALUES (@addrid, @voutid);
`;
}
sync_sql += `
INSERT INTO TransactionVouts (TransactionId, VoutId, direction)
VALUES (@txid, @voutid, 1);
`;
}
// Loop over vin's
for (var i = 0; i < tx.vin.length; i++) {
const vin = tx.vin[i];
if (vin.txid) {
sync_sql += `
SET @vin = (
SELECT Vouts.id
FROM Vouts
INNER JOIN TransactionVouts
ON Vouts.id=TransactionVouts.VoutId
INNER JOIN Transactions
ON Transactions.id=TransactionVouts.TransactionId
WHERE
TransactionVouts.direction=1 AND
Transactions.txid="${vin.txid}" AND
Vouts.n=${vin.vout}
);
INSERT INTO TransactionVouts (TransactionId, VoutId, direction)
VALUES (@txid, @vin, 0);
`
}
}
}
async function syncNextBlock(syncedHeight) {
const height = syncedHeight + 1;
sync_sql = '';
const res_hash = await MakeRPCRequest(JSON.stringify({
method: 'getblockhash',
params: [height],
id: 1
}));
const blockHash = JSON.parse(res_hash)['result'];
const res_block = await MakeRPCRequest(JSON.stringify({
method: 'getblock',
params: [blockHash],
id: 1
}));
const block = JSON.parse(res_block)['result'];
const res_blockhr = await MakeRPCRequest(JSON.stringify({
method: 'getnetworkhashps',
params: [120, height],
id: 1
}));
block.hashrate = JSON.parse(res_blockhr)['result'];
block.time = moment(block.time*1000).utc().format('YYYY-MM-DD HH:mm:ss Z');
sync_sql = `
SET autocommit = 0;
START TRANSACTION;
INSERT INTO Block (
hash,
height,
size,
version,
merkleroot,
time,
nonce,
bits,
difficulty,
hashrate,
previousblockhash
)
VALUES (
"${block.hash}",
"${block.height}",
"${block.size}",
"${block.version}",
"${block.merkleroot}",
"${block.time}",
"${block.nonce}",
"${block.bits}",
"${block.difficulty}",
"${block.hashrate}",
"${block.previousblockhash}"
);
`
coolstrs = []
for (var i = 0; i < block.tx.length; i++) {
await saveTransaction(block.tx[i], block.height);
coolstrs.push(`${block.tx[i]} - ${block.time}`);
}
if (block.height > 1) {
sync_sql += `
UPDATE Block
SET nextblockhash="${block.hash}"
WHERE hash="${block.previousblockhash}";
`
}
sync_sql += 'COMMIT;'
await models.sequelize.query(sync_sql);
return height;
}
async function getCurrentHeight() {
const result = await MakeRPCRequest(JSON.stringify({
method: 'getblockcount',
params: [],
id: 1
}));
return JSON.parse(result)['result'] - HeightOffset;
}
async function getSyncedHeight() {
const result = await models.Block.findOne({
attributes: ['height'],
order: [['height', 'DESC']],
limit: 1
});
const height = result ? result.height : -1;
return height;
}
async function acquireLock() {
let fd = fs.openSync('sync.lock', 'w');
try {
fs.flockSync(fd, 'exnb');
} catch(ex) {
if (ex.code === 'EAGAIN') {
console.log('Synchronization is already running');
} else {
console.log('Could\'nt lock file', ex);
}
process.exit(0);
}
}
async function syncBlockchain() {
try {
await acquireLock();
let syncedHeight = await getSyncedHeight();
console.log('\x1b[36m%s\x1b[0m', 'syncedHeight is', syncedHeight);
let currentHeight = await getCurrentHeight();
console.log('\x1b[34m%s\x1b[0m', 'currentHeight is', currentHeight);
while (syncedHeight < currentHeight) {
starttime = new Date().getTime();
syncedHeight = await syncNextBlock(syncedHeight);
if (coolstrs) {
for(str of coolstrs) {
console.log('\x1b[36m%s\x1b[0m', `syncedHeight: ${syncedHeight}/${currentHeight}`, str, ' [', new Date().getTime() - starttime, 'ms ]')
}
} else {
console.log('\x1b[36m%s\x1b[0m', 'syncedHeight: ', syncedHeight)
}
}
} catch (e) {
console.log(e);
} finally {
await models.sequelize.close();
process.exit(0);
}
}
syncBlockchain();