fix(tokenStats): Added generateCredentials() to tokenStats SLP method

This commit is contained in:
Chris Troutner
2020-05-11 21:13:48 -07:00
parent dd5008507c
commit cc17916022
3 changed files with 306 additions and 8 deletions
+286
View File
@@ -0,0 +1,286 @@
const axios = require('axios')
const SLPSDK = require('@chris.troutner/bch-js')
const SLP = new SLPSDK()
class Slpdb {
async getHistoricalSlpTransactions (addressList, fromBlock) {
// Build SLPDB or query from addressList
const orQueryArray = []
for (const address of addressList) {
const cashAddress = SLP.Address.toCashAddress(address)
const slpAddress = SLP.Address.toSLPAddress(address)
const cashQuery = {
'in.e.a': cashAddress.slice(12)
}
const slpQuery = {
'slp.detail.outputs.address': slpAddress
}
orQueryArray.push(cashQuery)
orQueryArray.push(slpQuery)
}
const query = {
v: 3,
q: {
find: {
db: ['c', 'u'],
$query: {
$or: orQueryArray,
'slp.valid': true,
'blk.i': {
$not: {
$lte: fromBlock
}
}
},
$orderby: {
'blk.i': -1
}
},
project: {
_id: 0,
'tx.h': 1,
'in.i': 1,
'in.e': 1,
'out.e': 1,
'out.a': 1,
'slp.detail': 1,
blk: 1
},
limit: 500
}
}
const result = await this.runQuery(query)
let transactions = []
if (result.data && result.data.c) {
transactions = transactions.concat(result.data.c)
}
if (result.data && result.data.u) {
transactions = transactions.concat(result.data.u)
}
return transactions
}
async getTokenStats (tokenId) {
const [totalMinted, totalBurned, tokenDetails] = await Promise.all([
this.getTotalMinted(tokenId),
this.getTotalBurned(tokenId),
this.getTokenDetails(tokenId)
])
tokenDetails.totalMinted = tokenDetails.initialTokenQty + totalMinted
tokenDetails.totalBurned = totalBurned
tokenDetails.circulatingSupply =
tokenDetails.totalMinted - tokenDetails.totalBurned
return tokenDetails
}
generateCredentials () {
// Generate the Basic Authentication header for a private instance of SLPDB.
const SLPDB_PASS = process.env.SLPDB_PASS
? process.env.SLPDB_PASS
: 'BITBOX'
const username = 'BITBOX'
const password = SLPDB_PASS
const combined = `${username}:${password}`
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
authorization: readyCredential,
timeout: 30000
}
}
return options
}
async runQuery (query) {
const queryString = JSON.stringify(query)
const queryBase64 = Buffer.from(queryString).toString('base64')
const url = `${process.env.SLPDB_URL}q/${queryBase64}`
const options = this.generateCredentials()
const response = await axios.get(url, options)
return response
}
async getTotalMinted (tokenId) {
const query = {
v: 3,
q: {
db: ['g'],
aggregate: [
{
$match: {
'tokenDetails.tokenIdHex': tokenId,
'graphTxn.outputs.status': {
$in: [
'BATON_SPENT_IN_MINT',
'BATON_UNSPENT',
'BATON_SPENT_NOT_IN_MINT'
]
}
}
},
{
$unwind: '$graphTxn.outputs'
},
{
$group: {
_id: null,
count: {
$sum: '$graphTxn.outputs.slpAmount'
}
}
}
],
limit: 1
}
}
const result = await this.runQuery(query)
if (!result.data.g.length) {
return 0
}
return parseFloat(result.data.g[0].count)
}
async getTotalBurned (tokenId) {
const query = {
v: 3,
q: {
db: ['g'],
aggregate: [
{
$match: {
'tokenDetails.tokenIdHex': tokenId,
'graphTxn.outputs.status': {
$in: [
'SPENT_NON_SLP',
'BATON_SPENT_INVALID_SLP',
'SPENT_INVALID_SLP',
'BATON_SPENT_NON_SLP',
'MISSING_BCH_VOUT',
'BATON_MISSING_BCH_VOUT',
'BATON_SPENT_NOT_IN_MINT',
'EXCESS_INPUT_BURNED'
]
}
}
},
{
$unwind: '$graphTxn.outputs'
},
{
$match: {
'graphTxn.outputs.status': {
$in: [
'SPENT_NON_SLP',
'BATON_SPENT_INVALID_SLP',
'SPENT_INVALID_SLP',
'BATON_SPENT_NON_SLP',
'MISSING_BCH_VOUT',
'BATON_MISSING_BCH_VOUT',
'BATON_SPENT_NOT_IN_MINT',
'EXCESS_INPUT_BURNED'
]
}
}
},
{
$group: {
_id: null,
count: {
$sum: '$graphTxn.outputs.slpAmount'
}
}
}
],
limit: 1
}
}
const result = await this.runQuery(query)
if (!result.data.g.length) {
return 0
}
return parseFloat(result.data.g[0].count)
}
async getTokenDetails (tokenId) {
const query = {
v: 3,
q: {
db: ['t'],
find: {
$query: {
'tokenDetails.tokenIdHex': tokenId
}
},
project: { tokenDetails: 1, tokenStats: 1, _id: 0 },
limit: 1
}
}
const result = await this.runQuery(query)
if (!result.data.t.length) {
throw new Error('Token could not be found')
}
const token = this.formatTokenOutput(result.data.t[0])
return token
}
formatTokenOutput (token) {
token.tokenDetails.id = token.tokenDetails.tokenIdHex
delete token.tokenDetails.tokenIdHex
token.tokenDetails.documentHash = token.tokenDetails.documentSha256Hex
delete token.tokenDetails.documentSha256Hex
token.tokenDetails.initialTokenQty = parseFloat(
token.tokenDetails.genesisOrMintQuantity
)
delete token.tokenDetails.genesisOrMintQuantity
delete token.tokenDetails.transactionType
delete token.tokenDetails.batonVout
delete token.tokenDetails.sendOutputs
token.tokenDetails.blockCreated = token.tokenStats.block_created
token.tokenDetails.blockLastActiveSend =
token.tokenStats.block_last_active_send
token.tokenDetails.blockLastActiveMint =
token.tokenStats.block_last_active_mint
token.tokenDetails.txnsSinceGenesis =
token.tokenStats.qty_valid_txns_since_genesis
token.tokenDetails.validAddresses =
token.tokenStats.qty_valid_token_addresses
token.tokenDetails.mintingBatonStatus =
token.tokenStats.minting_baton_status
delete token.tokenStats.block_last_active_send
delete token.tokenStats.block_last_active_mint
delete token.tokenStats.qty_valid_txns_since_genesis
delete token.tokenStats.qty_valid_token_addresses
token.tokenDetails.timestampUnix = token.tokenDetails.timestamp_unix
delete token.tokenDetails.timestamp_unix
return token.tokenDetails
}
}
module.exports = Slpdb
+12 -1
View File
@@ -7,6 +7,9 @@ const BigNumber = require('bignumber.js')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const Slpdb = require('./services/slpdb')
// const strftime = require('strftime')
const wlogger = require('../../util/winston-logging')
@@ -50,6 +53,7 @@ class Slp {
_this.BigNumber = BigNumber
_this.bchjs = bchjs
_this.rawTransactions = rawTransactions
_this.slpdb = new Slpdb()
_this.router = router
@@ -1410,10 +1414,17 @@ class Slp {
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentials()
// Request options
const opt = {
method: 'get',
baseURL: url
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
// Get data from BitDB.
const tokenRes = await _this.axios.request(opt)
+8 -7
View File
@@ -461,17 +461,18 @@ describe('#SLP', () => {
})
})
describe('tokenStatsSingle()', () => {
const tokenStatsSingle = slpRoute.tokenStats
describe('tokenStats()', () => {
const tokenStats = slpRoute.tokenStats
it('should throw 400 if tokenID is empty', async () => {
req.params.tokenId = ''
const result = await tokenStatsSingle(req, res)
const result = await tokenStats(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'tokenId can not be empty')
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' })
@@ -479,7 +480,7 @@ describe('#SLP', () => {
req.params.tokenId =
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
const result = await tokenStatsSingle(req, res)
const result = await tokenStats(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
@@ -497,7 +498,7 @@ describe('#SLP', () => {
req.params.tokenId =
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
const result = await tokenStatsSingle(req, res)
const result = await tokenStats(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
@@ -507,7 +508,7 @@ describe('#SLP', () => {
'Error message expected'
)
})
//
it('should get token stats for tokenId', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
@@ -526,7 +527,7 @@ describe('#SLP', () => {
req.params.tokenId =
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
const result = await tokenStatsSingle(req, res)
const result = await tokenStats(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAnyKeys(result, [