Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor backend #46

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 48 additions & 275 deletions packages/api/index.js
Original file line number Diff line number Diff line change
@@ -1,295 +1,68 @@
const express = require('express');
const fetch = require('node-fetch')
const app = express();
const cors = require('cors')
const Dash = require('dash')
const crypto = require('crypto')
const bodyParser = require('body-parser')

const cache = require('./src/cache')

const BASE_URL = process.env.BASE_URL
const BLOCK_TIME = 3 * 1000;

app.use(cors())
const jsonParser = bodyParser.json()

class ServiceNotAvailableError extends Error {
}

// const worker = new Worker()
// worker.setHandler(async () => {
// try {
// } catch (e) {
// }
// })
// worker.start(3000)

const hash = (data) => {
return crypto.createHash('sha1').update(data).digest('hex');
}

const client = new Dash.Client()

const call = async (path, method, body) => {
console.log(`Request for the ${BASE_URL}/${path}`)

try {
const response = await fetch(`${BASE_URL}/${path}`, {
method,
body,
headers: {
'content-type': 'application/json'
}
})
require('dotenv').config()

if (response.status === 200) {
return response.json()
} else {
const text = await response.text()
console.error(text)
throw new Error(`Unknown status code from Tenderdash RPC (${response.status})`)
}
} catch (e) {
console.error(e)
throw new ServiceNotAvailableError()
}
}

function errorHandler(err, req, res, next) {
const Dash = require('dash')
const Fastify = require('fastify')
const cors = require('@fastify/cors')
const Routes = require('./src/routes')

const ServiceNotAvailableError = require("./src/errors/ServiceNotAvailableError");
const MainController = require("./src/controllers/MainController");
const TransactionController = require("./src/controllers/TransactionController");
const BlockController = require("./src/controllers/BlockController");
const TenderdashRPC = require("./src/tenderdashRpc");
const packageVersion = require('./package.json').version
const Worker = require('./src/worker/index')
const {BLOCK_TIME} = require("./src/constants");

function errorHandler(err, req, reply) {
if (err instanceof ServiceNotAvailableError) {
res.status(403)
return res.send({error: 'tenderdash backend is not available'})
}

console.error(err)
res.status(500)

res.send({error: err.message})
}


app.get('/status', async (req, res, next) => {
try {
const cached = cache.get('status')

if (cached) {
return res.send(cached)
}

const {sync_info, node_info} = await call('status', 'GET')
const {latest_block_height} = sync_info
const {network, protocol_version} = node_info

const tenderdashVersion = node_info.version
const appVersion = protocol_version.app
const p2pVersion = protocol_version.p2p
const blockVersion = protocol_version.block
const blocksCount = latest_block_height

const status = {
network,
appVersion,
p2pVersion,
blockVersion,
blocksCount,
tenderdashVersion
}

cache.set('status', status, BLOCK_TIME)

res.send(status);
} catch (e) {
next(e)
return reply.status(403).send({error: 'tenderdash backend is not available'})
}
});

app.get('/block/:hash', async (req, res, next) => {
const {hash} = req.params;

try {
const cached = cache.get('block_' + hash)

if (cached) {
return res.send(cached)
}

const block = await call(`block_by_hash?hash=${hash}`, 'GET')
if (err?.constructor?.name === 'InvalidStateTransitionError') {
const [error] = err.getErrors()
const {code, message} = error

if (block?.block?.data?.txs?.length) {
const txHashes = block.block.data.txs.map(tx => crypto.createHash('sha256').update(Buffer.from(tx, 'base64')).digest('hex').toUpperCase())

Object.assign(block, {txHashes})
}

cache.set('block_' + hash, block);

res.send(block);
} catch (e) {
next(e)
return reply.status(500).send({error: message, code})
}
});

app.get('/blocks', async (req, res, next) => {
try {
const {page, limit, order} = req.query

let query = 'block_search?query=block.height>=1'

if (page) {
query += '&page=' + page
}

if (limit) {
query += '&per_page=' + limit
} else {
query += '&per_page=' + 30
}

if (order) {
query += '&order=' + order
} else {
query += '&order=desc'
}

const blocks = await call(query, 'GET')
res.send(blocks);
} catch (e) {
next(e)
}
});

app.get('/transactions', async (req, res, next) => {
try {
const {from, to} = req.query

let query = `tx_search?query=`

if (from) {
query += `tx.height>${from}`
} else {
query += `tx.height>1`
}

if (to) {
query += `%20AND%20tx.height<${to}`
}

const cached = cache.get('tx_search_' + hash(query))

if (cached) {
return res.send(cached)
}

const transactions = await call(`tx_search?query=tx.height>1`, 'GET')

cache.set('tx_search_' + hash(query))

res.send(transactions);

} catch (e) {
next(e)
}
});

app.get('/transaction/:txHash', async (req, res, next) => {
try {
const {txHash} = req.params;

const cached = cache.get('transaction_' + txHash)

if (cached) {
return res.send(cached)
}

const transaction = await call(`tx?hash=${txHash}`, 'GET')

cache.set('transaction_' + txHash, transaction)

res.send(transaction)
} catch (e) {
next(e)
}
});

app.get('/search', async (req, res, next) => {
try {
const {query} = req.query;

// todo validate
if (!query) {
return res.status(400).send({error: '`?query=` missing'})
}

const cached = cache.get('search_' + hash(query))

if (cached) {
return res.send(cached)
}

if (/^[0-9]/.test(query)) {
const block = await call(`block?height=${query}`, 'GET')

if (!block.code) {
cache.set('search_' + hash(query), {block})

return res.send({block})
}
}

// search blocks
const block = await call(`block_by_hash?hash=${query}`, 'GET')

if (!block.code && block.block_id.hash) {
cache.set('search_' + hash(query), {block})

return res.send({block})
}

// search transactions
const transaction = await call(`tx?hash=${query}`, 'GET')
console.error(err)
reply.status(500)

if (!transaction.code) {
cache.set('search_' + hash(query), {transaction})
reply.send({error: err.message})
}

return res.send({transaction})
}
let status;

res.status(404).send({message: 'not found'})
} catch (e) {
next(e)
}
});
const init = async () => {
const client = new Dash.Client()
await client.platform.initialize()

app.post('/transaction/decode', jsonParser, async (req, res, next) => {
try {
const {base64} = req.body;
const worker = new Worker()

const cached = cache.get('decoded_' + base64)

if (cached) {
return res.send(cached)
worker.setHandler(async () => {
try {
status = await TenderdashRPC.getStatus()
} catch (e) {
}
})
worker.start(BLOCK_TIME)

const stateTransition = await client.platform.dpp.stateTransition.createFromBuffer(Buffer.from(base64, 'base64'));
const fastify = Fastify()

cache.set('decoded_' + base64, stateTransition)
await fastify.register(cors, {
// put your options here
})

res.send(stateTransition)
} catch (e) {
if (e?.constructor?.name === 'InvalidStateTransitionError') {
const [error] = e.getErrors()
const {code, message} = error
const mainController = new MainController()
const blockController = new BlockController()
const transactionController = new TransactionController(client)

return res.status(500).send({error: message, code})
}
Routes({fastify, mainController, blockController, transactionController})

res.status(500).send({error: e.message})
}
});

app.use(errorHandler)
fastify.setErrorHandler(errorHandler)
fastify.listen({port: 3005, listenTextResolver: (address) => console.log(`Platform indexer API has started on the ${address}`)});
}

client.platform.initialize().then(() => app.listen(3005))

console.log('Api started')
init().then((`Platform Explorer backend v${packageVersion}`))
6 changes: 3 additions & 3 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"start": "node index.js"
},
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"@fastify/cors": "^8.3.0",
"dash": "^3.25.0-dev.21",
"express": "^4.18.2",
"dotenv": "^16.3.1",
"fastify": "^4.21.0",
"node-fetch": "^2.6.11"
}
}
File renamed without changes.
3 changes: 3 additions & 0 deletions packages/api/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
BLOCK_TIME: 5000
}
Loading