Skip to content

Commit

Permalink
feat: add tari_scanner (#4280)
Browse files Browse the repository at this point in the history
Description
---
Tari scanner app.
Start by running `npm start` in `applications/tari_scanner`. Don't forget to run `npm install` first.
You have to have a base node running.
  • Loading branch information
Cifko committed Jul 20, 2022
1 parent f6342a5 commit bd672eb
Show file tree
Hide file tree
Showing 27 changed files with 4,492 additions and 3 deletions.
1 change: 1 addition & 0 deletions .license.ignore
Expand Up @@ -13,6 +13,7 @@
./applications/tari_base_node/osx-pkg/scripts/preinstall
./applications/tari_console_wallet/linux/start_tari_console_wallet
./applications/tari_explorer/bin/www
./applications/tari_scanner/bin/www
./base_layer/core/src/proof_of_work/lwma_diff.rs
./base_layer/key_manager/Makefile
./base_layer/p2p/src/dns/roots/tls.rs
Expand Down
8 changes: 6 additions & 2 deletions applications/tari_explorer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 88 additions & 0 deletions applications/tari_scanner/app.js
@@ -0,0 +1,88 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");

var indexRouter = require("./routes/index");
var contractRouter = require("./routes/contract");
var validatorRouter = require("./routes/validatorNode");
var dataRouter = require("./routes/data");
var livereload = require("livereload");
var connectLiveReload = require("connect-livereload");
const liveReloadServer = livereload.createServer();
liveReloadServer.server.once("connection", () => {
setTimeout(() => {
liveReloadServer.refresh("/");
}, 100);
});

var hbs = require("hbs");
const { title } = require("process");
hbs.registerHelper("hex", function (buffer) {
return buffer ? Buffer.from(buffer).toString("hex") : "";
});
hbs.registerHelper("concat", function (string1, string2) {
return string1 + string2;
});
hbs.registerHelper("times", function (n, block) {
var accum = "";
for (var i = 0; i < n; ++i) accum += block.fn({ index: i, render: `render-${i}` });
return accum;
});
hbs.registerHelper("table", function (block) {
let { id, endpoint, rows } = block.hash;
let table_navigation = `<div class="navigation"><span id="${id}Prev" class="prev clickable">&lt Prev</span><span id="${id}Pages" class="pages"></span><span id="${id}Next" class="next clickable">Next &gt</span></div>`;
let header = block.fn(block.hash);
let cols = (header.match(/<\/th>/g) || []).length;
let row_data = "";
for (let i = 0; i < rows; ++i) {
row_data += "<tr>";
for (let j = 0; j < cols; ++j) {
row_data += `<td id="${id}-td-${i}-${j}"></td>`;
}
row_data += "</tr>";
}
return `<table id="${id}-table" endpoint="${endpoint}" cols=${cols} rows=${rows}><thead><tr>${header}</tr></thead><tbody id="${id}Body">${row_data}</tbody></table>${table_navigation}`;
});
hbs.registerPartials(path.join(__dirname, "components"));
// hbs.registerHelper("sort", function (list, ))

var app = express();
app.use(connectLiveReload());

// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "hbs");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use("/", indexRouter);
app.use("/validator_node", validatorRouter);
app.use("/contract", contractRouter);
app.use("/data", dataRouter);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};

// render the error page
res.status(err.status || 500);
res.render("error");
});

module.exports = app;
154 changes: 154 additions & 0 deletions applications/tari_scanner/baseNodeClient.js
@@ -0,0 +1,154 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

var { Client } = require("@tari/base-node-grpc-client");
const { contracts } = require("./helpers/contracts");
const { validator_nodes } = require("./helpers/validatorNodes");
var { range } = require("./utils");

class ExpressClient {
constructor() {
this.client = Client.connect("localhost:18142");
this.blocks = {};
this.validator_nodes = validator_nodes;
this.contracts = contracts;
}

getTip() {
return this.client.getTipInfo().then((tip_info) => parseInt(tip_info.metadata.height_of_longest_chain));
}

#getBlocksArray(from, to) {
let result = [];
for (var i = from; i < to; ++i) {
if (i in this.blocks) {
result.push(this.blocks[i]);
}
}
return result;
}

getBlocks(from, to, refresh = false) {
let heights = range(from, to);
if (!refresh) {
heights = heights.filter((height) => !(height in this.blocks));
}
if (heights.length) {
return this.client.getBlocks({ heights: heights }).then((blocks) => {
for (const block of blocks) {
const height = parseInt(block.block.header.height);
this.blocks[height] = block.block;
}
return this.#getBlocksArray(from, to);
});
} else {
return this.#getBlocksArray(from, to);
}
}

#updateFromContractDefinition(sidechain_features, height) {
let contract_id = sidechain_features.contract_id.toString("hex");
let contract = contracts.addContract(contract_id, height);
contract.name = sidechain_features.definition.contract_name.toString();
contract.issuer = sidechain_features.definition.contract_issuer.toString("hex");
}

#updateFromContractConstitution(sidechain_features) {
let contract_id = sidechain_features.contract_id.toString("hex");
for (let member of sidechain_features.constitution.validator_committee.members) {
member = member.toString("hex");
validator_nodes.addValidatorNode(member).addContract(contract_id);
contracts.addContract(contract_id).addToCommittee(member);
}
}

#updateFromContractValidatorAcceptance(sidechain_features, height) {
let contract_id = sidechain_features.contract_id.toString("hex");
let member = sidechain_features.acceptance.validator_node_public_key.toString("hex");
validator_nodes.addValidatorNode(member).addContract(contract_id, height);
contracts.addContract(contract_id).addToCommittee(member);
}

#updateFromContractCheckpoint(sidechain_features, height) {
let contract_id = sidechain_features.contract_id.toString("hex");
let signatures = sidechain_features?.checkpoint?.signatures?.signatures;
if (contract_id != "b5ad8929ca1026d7411633f7cde8955c1150f5747660d635c85a3f684ebd47c5")
signatures = [{ public_nonce: "5c50fcb6b0966e595c3c9b121443989ec78c6d3ac45ba98edd1dcc5d39c2f665" }];
if (signatures) {
let validators = signatures.map((signature) => signature.public_nonce);
for (const validator of validators) {
validator_nodes.addValidatorNode(validator).addCheckpoint(contract_id, height);
}
contracts.addContract(contract_id).addCheckpoint(height, validators);
}
}

#updateFromContractConstitutionProposal(sidechain_features) {
let contract_id = sidechain_features.contract_id.toString("hex");
for (let member of sidechain_features.update_proposal.updated_constitution.validator_committee.members) {
member = member.toString("hex");
validator_nodes.addValidatorNode(member).addContract(contract_id);
contracts.addContract(contract_id).addToCommittee(member);
}
}

#updateFromContractConstitutionChangeAcceptance(sidechain_features) {
console.log("updateFromContractConstitutionChangeAcceptance");
}

async updateAllValidatorNodes() {
let tip = await this.getTip();
let blocks = await this.getBlocks(1, tip + 1);
for (const block of blocks) {
let height = block.header.height;
for (const output of block.body.outputs) {
switch (output.features.output_type) {
case 2: // CONTRACT_DEFINITION
this.#updateFromContractDefinition(output.features.sidechain_features, height);
break;
case 3: // CONTRACT_CONSTITUTION
this.#updateFromContractConstitution(output.features.sidechain_features);
break;
case 4: // CONTRACT_VALIDATOR_ACCEPTANCE
this.#updateFromContractValidatorAcceptance(output.features.sidechain_features, height);
break;
case 5: // CONTRACT_CHECKPOINT
this.#updateFromContractCheckpoint(output.features.sidechain_features, height);
break;
case 6: // CONTRACT_CONSTITUTION_PROPOSAL
this.#updateFromContractConstitutionProposal(output.features.sidechain_features);
break;
case 7: // CONTRACT_CONSTITUTION_CHANGE_ACCEPTANCE
this.#updateFromContractConstitutionChangeAcceptance(output.features.sidechain_features);
break;
}
}
}
}

async getAllContracts() {
await this.updateAllValidatorNodes();
return this.contracts.getAllIDs();
}

async getAllValidatorNodes() {
await this.updateAllValidatorNodes();
return this.validator_nodes.getAllIDs();
}

async getValidatorNode(id) {
await this.updateAllValidatorNodes();
return this.validator_nodes.getValidatorNode(id);
}

async getContract(id) {
await this.updateAllValidatorNodes();
return this.contracts.getContract(id);
}
}

client = new ExpressClient();

module.exports = {
client,
};
90 changes: 90 additions & 0 deletions applications/tari_scanner/bin/www
@@ -0,0 +1,90 @@
#!/usr/bin/env node

/**
* Module dependencies.
*/

var app = require('../app');
var debug = require('debug')('tari-scanner:server');
var http = require('http');

/**
* Get port from environment and store in Express.
*/

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

0 comments on commit bd672eb

Please sign in to comment.