1214 lines
44 KiB
JavaScript
1214 lines
44 KiB
JavaScript
let lastBlock = {
|
|
height: -1,
|
|
id: '0000000000000000000000000000000000000000000000000000000000000000'
|
|
}
|
|
|
|
let blockInfo = {}
|
|
let now_blocks_sync = false
|
|
|
|
// market
|
|
let now_delete_offers = false
|
|
|
|
// pool
|
|
let countTrPoolServer
|
|
let statusSyncPool = false
|
|
|
|
// aliases
|
|
let countAliasesDB
|
|
let countAliasesServer
|
|
|
|
// alt_blocks
|
|
let countAltBlocksDB = 0
|
|
let countAltBlocksServer
|
|
let statusSyncAltBlocks = false
|
|
|
|
let block_array = []
|
|
let pools_array = []
|
|
|
|
let serverTimeout = 30
|
|
|
|
|
|
// Aliases
|
|
app.get(
|
|
'/api/get_aliases/:offset/:count/:search',
|
|
exceptionHandler(async (req, res, next) => {
|
|
let offset = parseInt(req.params.offset)
|
|
let count = parseInt(req.params.count)
|
|
if (count > maxCount) {
|
|
count = maxCount
|
|
}
|
|
let search = req.params.search.toLowerCase()
|
|
|
|
if (search === 'all' && offset !== undefined && count !== undefined) {
|
|
const query = {
|
|
text: 'SELECT * FROM aliases WHERE enabled = 1 ORDER BY block DESC limit $1 offset $2;',
|
|
values: [count, offset]
|
|
}
|
|
let result = await db.query(query)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
} else if (
|
|
search !== undefined &&
|
|
offset !== undefined &&
|
|
count !== undefined
|
|
) {
|
|
let result = await db.query(
|
|
`SELECT * FROM aliases WHERE enabled = 1 AND (alias LIKE '%${search}%' OR address LIKE '%${search}%' OR comment LIKE '%${search}%') ORDER BY block DESC limit ${count} offset ${offset};`
|
|
)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
}
|
|
})
|
|
)
|
|
|
|
// Charts
|
|
app.get(
|
|
'/api/get_chart/:chart/:period',
|
|
exceptionHandler(async (req, res) => {
|
|
let chart = req.params.chart
|
|
let period = req.params.period
|
|
if (chart !== undefined) {
|
|
let period = Math.round(new Date().getTime() / 1000) - 24 * 3600 // + 86400000
|
|
let period2 = Math.round(new Date().getTime() / 1000) - 48 * 3600 // + 86400000
|
|
if (chart === 'all') {
|
|
//convert me into a sp or view[sqllite3] please!!
|
|
let arrayAll = await db.query(
|
|
`SELECT actual_timestamp::bigint as at, block_cumulative_size::real as bcs, tr_count::real as trc, difficulty::real as d, type as t FROM charts WHERE actual_timestamp > ${period} ORDER BY at;`
|
|
)
|
|
let rows0 = await db.query(
|
|
`SELECT extract(epoch from date_trunc('day', to_timestamp(actual_timestamp))) as at, SUM(tr_count)::real as sum_trc FROM charts GROUP BY at ORDER BY at;`
|
|
)
|
|
let rows1 = await db.query(
|
|
`SELECT actual_timestamp as at, difficulty120::real as d120, hashrate100::real as h100, hashrate400::real as h400 FROM charts WHERE type=1 AND actual_timestamp > ${period2} ORDER BY at;`
|
|
)
|
|
arrayAll.rows[0] = rows0.rows
|
|
arrayAll.rows[1] = rows1.rows
|
|
res.json(arrayAll.rows)
|
|
} else if (chart === 'AvgBlockSize') {
|
|
result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('hour', to_timestamp(actual_timestamp))) as at, avg(block_cumulative_size)::real as bcs FROM charts GROUP BY at ORDER BY at;`
|
|
)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
} else if (chart === 'AvgTransPerBlock') {
|
|
result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('hour', to_timestamp(actual_timestamp))) as at, avg(tr_count)::real as trc FROM charts GROUP BY at ORDER BY at;`
|
|
)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
} else if (chart === 'hashRate') {
|
|
result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('hour', to_timestamp(actual_timestamp))) as at, avg(difficulty120) as d120, avg(hashrate100) as h100, avg(hashrate400) as h400 FROM charts WHERE type=1 GROUP BY at ORDER BY at;`
|
|
)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
} else if (chart === 'pos-difficulty') {
|
|
let result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('hour', to_timestamp(actual_timestamp))) as at, case when (max(difficulty)-avg(difficulty))>(avg(difficulty)-min(difficulty)) then max(difficulty) else min(difficulty) end as d FROM charts WHERE type=0 GROUP BY at ORDER BY at;`
|
|
)
|
|
let result1 = await db.query(
|
|
'SELECT actual_timestamp as at, difficulty as d FROM charts WHERE type=0 ORDER BY at;'
|
|
)
|
|
res.json({
|
|
aggregated: result.rows,
|
|
detailed: result1.rows
|
|
})
|
|
} else if (chart === 'pow-difficulty') {
|
|
let result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('hour', to_timestamp(actual_timestamp))) as at, case when (max(difficulty)-avg(difficulty))>(avg(difficulty)-min(difficulty)) then max(difficulty) else min(difficulty) end as d FROM charts WHERE type=1 GROUP BY at ORDER BY at;`
|
|
)
|
|
let result1 = await db.query(
|
|
'SELECT actual_timestamp as at, difficulty as d FROM charts WHERE type=1 ORDER BY at;'
|
|
)
|
|
res.json({
|
|
aggregated: result.rows,
|
|
detailed: result1.rows
|
|
})
|
|
} else if (chart === 'ConfirmTransactPerDay') {
|
|
let result = await db.query(
|
|
`SELECT extract(epoch from date_trunc('day', to_timestamp(actual_timestamp)))::integer as at, SUM(tr_count)::integer as sum_trc FROM charts GROUP BY at ORDER BY at;`
|
|
)
|
|
res.json(result && result.rowCount > 0 ? result.rows : [])
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
app.get(
|
|
"/api/get_tx_by_keyimage/:id",
|
|
exceptionHandler(async (req, res, next) => {
|
|
const id = req.params.id.toLowerCase();
|
|
const txs = (await db.query("SELECT * FROM transactions WHERE ins LIKE $1", ['%' + id + '%'])).rows;
|
|
for (const tx of txs) {
|
|
try {
|
|
const ins = JSON.parse(tx.ins);
|
|
if (!(ins instanceof Array)) continue;
|
|
if (ins.find(e => e.kimage_or_ms_id === id)) {
|
|
return res.json({ result: "FOUND", data: tx.id });
|
|
}
|
|
} catch { }
|
|
}
|
|
return res.json({ result: "NOT FOUND" });
|
|
})
|
|
)
|
|
|
|
// Search
|
|
app.get(
|
|
'/api/search_by_id/:id',
|
|
exceptionHandler(async (req, res, next) => {
|
|
let id = req.params.id.toLowerCase()
|
|
if (!!id) {
|
|
let result = await db.query(
|
|
`SELECT * FROM blocks WHERE id = '${id}' ;`
|
|
)
|
|
if (!result || result.rowCount === 0) {
|
|
result = await db.query(
|
|
`SELECT * FROM alt_blocks WHERE hash = '${id}' ;`
|
|
)
|
|
if (!result || result.rowCount === 0) {
|
|
result = await db.query(
|
|
`SELECT * FROM transactions WHERE id = '${id}' ;`
|
|
)
|
|
if (!result || result.rowCount === 0) {
|
|
try {
|
|
let response = await get_tx_details(id)
|
|
if (response.data.result) {
|
|
res.json({ result: 'tx' })
|
|
} else {
|
|
let result = await db.query(
|
|
`SELECT * FROM aliases WHERE enabled = 1 AND (alias LIKE '%${id}%' OR address LIKE '%${id}%' OR comment LIKE '%${id}%') ORDER BY block DESC limit 1 offset 0;`
|
|
)
|
|
if (result.rowCount > 0) {
|
|
res.json({ result: 'alias' })
|
|
} else {
|
|
res.json({ result: 'NOT FOUND' })
|
|
}
|
|
}
|
|
} catch (error) {
|
|
res.json({ result: 'NOT FOUND' })
|
|
}
|
|
} else {
|
|
res.json({ result: 'tx' })
|
|
}
|
|
} else {
|
|
res.json({ result: 'alt_block' })
|
|
}
|
|
} else {
|
|
res.json({ result: 'block' })
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
const start = async () => {
|
|
try {
|
|
await db.query('DELETE FROM alt_blocks;')
|
|
let result = await db.query(
|
|
'SELECT * FROM blocks WHERE height=(SELECT MAX(height) FROM blocks);'
|
|
)
|
|
if (result && result.rowCount === 1) {
|
|
lastBlock = result.rows[0]
|
|
}
|
|
result = await db.query(
|
|
'SELECT COUNT(*)::integer AS alias FROM aliases;'
|
|
)
|
|
if (result) countAliasesDB = result.rows[0].alias
|
|
|
|
result = await db.query(
|
|
'SELECT COUNT(*)::integer AS height FROM alt_blocks;'
|
|
)
|
|
if (result) countAltBlocksDB = result.rows[0].height
|
|
getInfoTimer()
|
|
} catch (error) {
|
|
log(`Start ERROR: ${error}`)
|
|
}
|
|
}
|
|
|
|
start()
|
|
|
|
const syncPool = async () => {
|
|
try {
|
|
statusSyncPool = true
|
|
countTrPoolServer = blockInfo.tx_pool_size
|
|
if (countTrPoolServer === 0) {
|
|
await db.query('DELETE FROM pool;')
|
|
statusSyncPool = false
|
|
io.emit('get_transaction_pool_info', JSON.stringify([]))
|
|
} else {
|
|
let response = await get_all_pool_tx_list()
|
|
if (response.data.result.ids) {
|
|
pools_array = response.data.result.ids
|
|
? response.data.result.ids
|
|
: []
|
|
try {
|
|
await db.query(
|
|
`DELETE FROM pool WHERE id NOT IN ( '${pools_array.join(
|
|
"','"
|
|
)}' )`
|
|
)
|
|
} catch (error) {
|
|
log(`Delete From Pool ERROR: ${error}`)
|
|
}
|
|
try {
|
|
let result = await db.query('SELECT id FROM pool')
|
|
let new_ids = []
|
|
for (let j = 0; j < pools_array.length; j++) {
|
|
let find = false
|
|
for (let i = 0; i < result.rows.length; i++) {
|
|
if (pools_array[j] === result.rows[i].id) {
|
|
find = true
|
|
break
|
|
}
|
|
}
|
|
if (!find) {
|
|
new_ids.push(pools_array[j])
|
|
}
|
|
}
|
|
|
|
if (new_ids.length) {
|
|
try {
|
|
let response = await get_pool_txs_details(new_ids)
|
|
if (
|
|
response.data.result &&
|
|
response.data.result.txs
|
|
) {
|
|
let txInserts = []
|
|
for (let tx of response.data.result.txs) {
|
|
txInserts.push(
|
|
`(${tx.blob_size},` +
|
|
`${tx.fee},` +
|
|
`'${tx.id}',` +
|
|
`${tx.timestamp}` +
|
|
` )`
|
|
)
|
|
}
|
|
if (txInserts.length > 0) {
|
|
await db.query('BEGIN')
|
|
let sql =
|
|
'INSERT INTO POOL (blob_size, fee, id, timestamp) VALUES ' +
|
|
txInserts.join(',')
|
|
await db.query(sql)
|
|
await db.query('COMMIT')
|
|
}
|
|
statusSyncPool = false
|
|
} else {
|
|
statusSyncPool = false
|
|
}
|
|
io.emit('get_transaction_pool_info', JSON.stringify(await getTxPoolDetails(0)))
|
|
} catch (error) {
|
|
statusSyncPool = false
|
|
}
|
|
} else {
|
|
statusSyncPool = false
|
|
}
|
|
} catch (error) {
|
|
log(`Select id from pool ERROR: ${error}`)
|
|
}
|
|
} else {
|
|
statusSyncPool = false
|
|
}
|
|
}
|
|
} catch (error) {
|
|
await db.query('DELETE FROM pool')
|
|
statusSyncPool = false
|
|
}
|
|
}
|
|
|
|
const syncTransactions = async () => {
|
|
if (block_array.length > 0) {
|
|
let blockInserts = []
|
|
let transactionInserts = []
|
|
let chartInserts = []
|
|
let outInfoInserts = []
|
|
for (const bl of block_array) {
|
|
//build transaction inserts
|
|
{
|
|
try {
|
|
if (bl.tr_count === undefined)
|
|
bl.tr_count = bl.transactions_details.length
|
|
if (bl.tr_out === undefined) bl.tr_out = []
|
|
|
|
while (
|
|
!!(localTr = bl.transactions_details.splice(0, 1)[0])
|
|
) {
|
|
let response = await get_tx_details(localTr.id)
|
|
let tx_info = response.data.result.tx_info;
|
|
for (let item of tx_info.extra) {
|
|
if (item.type === 'alias_info') {
|
|
let arr = item.short_view.split('-->')
|
|
let aliasName = arr[0]
|
|
let aliasAddress = arr[1]
|
|
let aliasComment = parseComment(
|
|
item.datails_view || item.details_view
|
|
)
|
|
let aliasTrackingKey = parseTrackingKey(
|
|
item.datails_view || item.details_view
|
|
)
|
|
let aliasBlock = bl.height
|
|
let aliasTransaction = localTr.id
|
|
await db.query(
|
|
`UPDATE aliases SET enabled=0 WHERE alias = '${aliasName}';`
|
|
)
|
|
let sql = ''
|
|
try {
|
|
sql =
|
|
`INSERT INTO aliases VALUES ('${decodeString(
|
|
aliasName
|
|
)}',` +
|
|
`'${aliasAddress}',` +
|
|
`'${decodeString(aliasComment)}',` +
|
|
`'${decodeString(aliasTrackingKey)}',` +
|
|
`${aliasBlock},` +
|
|
`'${aliasTransaction}',` +
|
|
`${1}` +
|
|
`) ON CONFLICT (alias) ` +
|
|
`DO UPDATE SET ` +
|
|
`alias='${decodeString(aliasName)}',` +
|
|
`address='${aliasAddress}',` +
|
|
`comment='${decodeString(
|
|
aliasComment
|
|
)}',` +
|
|
`tracking_key='${decodeString(
|
|
aliasTrackingKey
|
|
)}',` +
|
|
`block='${aliasBlock}',` +
|
|
`transact='${aliasTransaction}',` +
|
|
`enabled=${1};`
|
|
await db.query(sql)
|
|
} catch (error) {
|
|
log(`SyncTransactions() Insert into aliases ERROR: ${error}\nsql: ${sql}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let item of tx_info.ins) {
|
|
if (item.global_indexes) {
|
|
bl.tr_out.push({
|
|
amount: item.amount,
|
|
i: item.global_indexes[0]
|
|
})
|
|
}
|
|
}
|
|
|
|
transactionInserts.push(
|
|
`('${tx_info.keeper_block}',` +
|
|
`'${tx_info.id}',` +
|
|
`'${tx_info.amount.toString()}',` +
|
|
`${tx_info.blob_size},` +
|
|
`'${decodeString(
|
|
JSON.stringify(tx_info.extra)
|
|
)}',` +
|
|
`${tx_info.fee},` +
|
|
`'${decodeString(
|
|
JSON.stringify(tx_info.ins)
|
|
)}',` +
|
|
`'${decodeString(
|
|
JSON.stringify(tx_info.outs)
|
|
)}',` +
|
|
`'${tx_info.pub_key}',` +
|
|
`${tx_info.timestamp},` +
|
|
`'${decodeString(
|
|
JSON.stringify(
|
|
!!tx_info.attachments
|
|
? tx_info.attachments
|
|
: {}
|
|
)
|
|
)}')`
|
|
)
|
|
}
|
|
} catch (error) {
|
|
log(`SyncTransactions() Inserting aliases ERROR: ${error}`)
|
|
}
|
|
}
|
|
|
|
chartInserts.push(
|
|
`(${bl.height},` +
|
|
`${bl.actual_timestamp},` +
|
|
`${bl.block_cumulative_size},` +
|
|
`${bl.cumulative_diff_precise},` +
|
|
`${bl.difficulty},` +
|
|
`${bl.tr_count ? bl.tr_count : 0},` +
|
|
`${bl.type},` +
|
|
`0,` +
|
|
`0,` +
|
|
`0)`
|
|
)
|
|
// }
|
|
|
|
//build out_info inserts
|
|
if (bl.tr_out && bl.tr_out.length > 0) {
|
|
for (let localOut of bl.tr_out) {
|
|
let localOutAmount = new BigNumber(
|
|
localOut.amount
|
|
).toNumber()
|
|
|
|
let response = await get_out_info(
|
|
localOutAmount,
|
|
localOut.i
|
|
)
|
|
|
|
outInfoInserts.push(
|
|
`(${localOut.amount},` +
|
|
`${localOut.i}, ` +
|
|
`'${response.data.result.tx_id}', ` +
|
|
`${bl.height})`
|
|
)
|
|
}
|
|
await db.query('begin')
|
|
//save out_info
|
|
{
|
|
try {
|
|
if (outInfoInserts.length > 0) {
|
|
let sql =
|
|
`INSERT INTO out_info VALUES ` +
|
|
outInfoInserts.join(',') +
|
|
`ON CONFLICT(amount, i, tx_id) DO NOTHING;`
|
|
await db.query(sql)
|
|
}
|
|
} catch (error) {
|
|
log(`SyncTransactions() Insert Into out_info ERROR: ${error}`)
|
|
}
|
|
}
|
|
await db.query('end')
|
|
}
|
|
|
|
//build block inserts
|
|
|
|
{
|
|
blockInserts.push(
|
|
`(${bl.height},` +
|
|
`${bl.actual_timestamp},` +
|
|
`${bl.base_reward},` +
|
|
`'${bl.blob}',` +
|
|
`${bl.block_cumulative_size},` +
|
|
`${bl.block_tself_size},` +
|
|
`${bl.cumulative_diff_adjusted},` +
|
|
`${bl.cumulative_diff_precise},` +
|
|
`${bl.difficulty},` +
|
|
`${bl.effective_fee_median},` +
|
|
`'${bl.id}',` +
|
|
`${bl.is_orphan},` +
|
|
`${bl.penalty},` +
|
|
`'${bl.prev_id}',` +
|
|
`${bl.summary_reward},` +
|
|
`${bl.this_block_fee_median},` +
|
|
`${bl.timestamp},` +
|
|
`${bl.total_fee},` +
|
|
`${bl.total_txs_size},` +
|
|
`${bl.tr_count ? bl.tr_count : 0},` +
|
|
`${bl.type},` +
|
|
"'" + decodeString(bl.miner_text_info) + "'," +
|
|
`${bl.already_generated_coins},` +
|
|
"'" + decodeString(bl.object_in_json) + "'," +
|
|
`'${bl.pow_seed}')`
|
|
);
|
|
}
|
|
}
|
|
|
|
await db.query('begin')
|
|
//save transactions
|
|
{
|
|
try {
|
|
if (transactionInserts.length > 0) {
|
|
let sql =
|
|
`INSERT INTO transactions VALUES ` +
|
|
transactionInserts.join(',') +
|
|
' ON CONFLICT (id) DO NOTHING;'
|
|
await db.query(sql)
|
|
}
|
|
} catch (error) {
|
|
log(`SyncTransactions() Insert Into transaction ERROR: ${error}`)
|
|
}
|
|
}
|
|
|
|
//save charts
|
|
{
|
|
try {
|
|
if (chartInserts.length > 0) {
|
|
let sql =
|
|
`INSERT INTO charts VALUES ` +
|
|
chartInserts.join(',') +
|
|
';'
|
|
await db.query(sql)
|
|
}
|
|
} catch (error) {
|
|
log(`SyncTransactions() Insert Into charts ERROR: ${error}`)
|
|
}
|
|
}
|
|
|
|
//save blocks
|
|
{
|
|
let sql = ''
|
|
if (blockInserts.length > 0) {
|
|
sql =
|
|
'INSERT INTO blocks (height,' +
|
|
'actual_timestamp,' +
|
|
'base_reward,' +
|
|
'blob,' +
|
|
'block_cumulative_size,' +
|
|
'block_tself_size,' +
|
|
'cumulative_diff_adjusted,' +
|
|
'cumulative_diff_precise,' +
|
|
'difficulty,' +
|
|
'effective_fee_median,' +
|
|
'id,' +
|
|
'is_orphan,' +
|
|
'penalty,' +
|
|
'prev_id,' +
|
|
'summary_reward,' +
|
|
'this_block_fee_median,' +
|
|
'timestamp,' +
|
|
'total_fee,' +
|
|
'total_txs_size,' +
|
|
'tr_count,' +
|
|
'type,' +
|
|
'miner_text_info,' +
|
|
'already_generated_coins,' +
|
|
'object_in_json,' +
|
|
'pow_seed) VALUES ' +
|
|
blockInserts.join(',') +
|
|
';'
|
|
await db.query(sql)
|
|
}
|
|
}
|
|
try {
|
|
await db.query('commit')
|
|
elementOne = block_array[0]
|
|
lastBlock = block_array.pop()
|
|
log(
|
|
`BLOCKS: db = ${lastBlock.height}/ server = ${blockInfo.height}`
|
|
)
|
|
await db.query(
|
|
`call update_statistics(${Math.min(
|
|
elementOne.height,
|
|
lastBlock.height
|
|
)})`
|
|
)
|
|
block_array = []
|
|
} catch (error) {
|
|
log(`SyncTransactions() Update_Statistics Store Proc ERROR: ${error}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
const syncBlocks = async () => {
|
|
try {
|
|
let count = blockInfo.height - lastBlock.height + 1
|
|
if (count > 100) {
|
|
count = 100
|
|
}
|
|
if (count < 0) {
|
|
count = 1
|
|
}
|
|
let response = await get_blocks_details(lastBlock.height + 1, count)
|
|
let localBlocks =
|
|
response.data.result && response.data.result.blocks
|
|
? response.data.result.blocks
|
|
: []
|
|
if (localBlocks.length && lastBlock.id === localBlocks[0].prev_id) {
|
|
block_array = localBlocks
|
|
await syncTransactions()
|
|
if (lastBlock.height >= blockInfo.height - 1) {
|
|
now_blocks_sync = false
|
|
enabled_during_sync = true
|
|
await emitSocketInfo()
|
|
} else {
|
|
await pause(serverTimeout)
|
|
await syncBlocks()
|
|
}
|
|
} else {
|
|
const deleteCount = 100
|
|
await db.query(
|
|
`CALL purgeAboveHeight(${lastBlock.height - deleteCount})`
|
|
)
|
|
const result = await db.query(
|
|
'SELECT * FROM blocks WHERE height=(SELECT MAX(height) FROM blocks);'
|
|
)
|
|
if (result) {
|
|
lastBlock = result.rows[0]
|
|
} else {
|
|
lastBlock = {
|
|
height: -1,
|
|
id: '0000000000000000000000000000000000000000000000000000000000000000'
|
|
}
|
|
}
|
|
await pause(serverTimeout)
|
|
await syncBlocks()
|
|
}
|
|
} catch (error) {
|
|
log(`SyncBlocks() get_blocks_details ERROR: ${error}`)
|
|
now_blocks_sync = false
|
|
}
|
|
}
|
|
|
|
const syncAltBlocks = async () => {
|
|
try {
|
|
statusSyncAltBlocks = true
|
|
await db.query('BEGIN')
|
|
await db.query('DELETE FROM alt_blocks')
|
|
let response = await get_alt_blocks_details(0, countAltBlocksServer)
|
|
for (let block of response.data.result.blocks) {
|
|
let sql =
|
|
`INSERT INTO alt_blocks(height, timestamp, actual_timestamp, size, hash, type, difficulty, cumulative_diff_adjusted, cumulative_diff_precise,` +
|
|
` is_orphan, base_reward, total_fee, penalty, summary_reward, block_cumulative_size, this_block_fee_median, effective_fee_median, total_txs_size, transactions_details, miner_txt_info, pow_seed) VALUES (` +
|
|
`${block.height},` +
|
|
`${block.timestamp},` +
|
|
`${block.actual_timestamp},` +
|
|
`${block.block_cumulative_size},` +
|
|
`'${block.id}',` +
|
|
`${block.type},` +
|
|
`${block.difficulty},` +
|
|
`${block.cumulative_diff_adjusted},` +
|
|
`${block.cumulative_diff_precise},` +
|
|
`${block.is_orphan},` +
|
|
`${block.base_reward},` +
|
|
`${block.total_fee},` +
|
|
`${block.penalty},` +
|
|
`${block.summary_reward},` +
|
|
`${block.block_cumulative_size},` +
|
|
`${block.this_block_fee_median},` +
|
|
`${block.effective_fee_median},` +
|
|
`${block.total_txs_size},` +
|
|
`'${JSON.stringify(block.transactions_details)}',` +
|
|
`'${block.miner_text_info
|
|
.replace('\u0000', '')
|
|
.replace("'", "''")}',` +
|
|
`''` +
|
|
`);`
|
|
await db.query(sql)
|
|
}
|
|
await db.query('COMMIT')
|
|
let result = await db.query(
|
|
'SELECT COUNT(*)::integer AS height FROM alt_blocks'
|
|
)
|
|
countAltBlocksDB = result && result.rowCount ? result.rows[0].height : 0
|
|
} catch (error) {
|
|
log(`SyncAltBlocks() ERROR: ${error}`)
|
|
await db.query('ROLLBACK')
|
|
}
|
|
statusSyncAltBlocks = false
|
|
}
|
|
|
|
const getInfoTimer = async () => {
|
|
if (now_delete_offers === false) {
|
|
try {
|
|
let response = await get_info();
|
|
blockInfo = response.data.result
|
|
countAliasesServer = blockInfo.alias_count
|
|
countAltBlocksServer = blockInfo.alt_blocks_count
|
|
countTrPoolServer = blockInfo.tx_pool_size
|
|
if (statusSyncPool === false) {
|
|
let result = await db.query(
|
|
'SELECT COUNT(*)::integer AS transactions FROM pool'
|
|
)
|
|
let countTrPoolDB = 0;
|
|
if (result && result.rowCount > 0)
|
|
countTrPoolDB = result.rows[0].transactions
|
|
if (countTrPoolDB !== countTrPoolServer) {
|
|
log(
|
|
`need to update pool transactions, db=${countTrPoolDB} server=${countTrPoolServer}`
|
|
)
|
|
await syncPool()
|
|
}
|
|
}
|
|
|
|
if (statusSyncAltBlocks === false) {
|
|
if (countAltBlocksServer !== countAltBlocksDB) {
|
|
log(
|
|
`need to update alt-blocks, db=${countAltBlocksDB} server=${countAltBlocksServer}`
|
|
)
|
|
await syncAltBlocks()
|
|
}
|
|
}
|
|
if (
|
|
lastBlock.height !== blockInfo.height - 1 &&
|
|
now_blocks_sync === false
|
|
) {
|
|
log(
|
|
`need to update blocks, db=${lastBlock.height} server=${blockInfo.height}`
|
|
)
|
|
let result = await db.query(
|
|
'SELECT COUNT(*)::integer AS height FROM aliases;'
|
|
)
|
|
countAliasesDB = result && result.rowCount ? result.rows[0].height : 0
|
|
if (countAliasesDB !== countAliasesServer) {
|
|
log(
|
|
`need to update aliases, db=${countAliasesDB} server=${countAliasesServer}`
|
|
)
|
|
}
|
|
now_blocks_sync = true
|
|
await syncBlocks()
|
|
await emitSocketInfo()
|
|
}
|
|
await pause(10000)
|
|
await getInfoTimer()
|
|
} catch (error) {
|
|
log(`getInfoTimer() ERROR: ${error}`)
|
|
blockInfo.daemon_network_state = 0
|
|
await pause(300000)
|
|
await getInfoTimer()
|
|
}
|
|
} else {
|
|
await pause(10000)
|
|
await getInfoTimer()
|
|
}
|
|
}
|
|
|
|
const pause = (ms) => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
}
|
|
|
|
// API
|
|
app.use(express.static(path.resolve(__dirname, "../build/")));
|
|
|
|
app.get(
|
|
'/api/get_info/:flags',
|
|
exceptionHandler(async (req, res) => {
|
|
let flags = req.params.flags
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: api,
|
|
data: {
|
|
method: 'getinfo',
|
|
params: { flags: parseInt(flags) }
|
|
}
|
|
})
|
|
res.json(response.data)
|
|
})
|
|
)
|
|
|
|
app.get(
|
|
'/api/get_total_coins',
|
|
exceptionHandler(async (req, res) => {
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: api,
|
|
data: {
|
|
method: 'getinfo',
|
|
params: { flags: parseInt(4294967295) }
|
|
}
|
|
})
|
|
|
|
let str = response.data.result.total_coins
|
|
let result
|
|
let totalCoins = Number(str)
|
|
if (typeof totalCoins === 'number') {
|
|
result = parseInt(totalCoins) / 1000000000000
|
|
}
|
|
let r2 = result.toFixed(2)
|
|
res.send(r2)
|
|
})
|
|
)
|
|
|
|
app.get(
|
|
'/api/get_blocks_details/:start/:count',
|
|
exceptionHandler(async (req, res) => {
|
|
let start = req.params.start
|
|
let count = req.params.count
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: api,
|
|
data: {
|
|
method: 'get_blocks_details',
|
|
params: {
|
|
height_start: parseInt(start ? start : 0),
|
|
count: parseInt(count ? count : 10),
|
|
ignore_transactions: false
|
|
}
|
|
}
|
|
})
|
|
res.json(response.data)
|
|
})
|
|
)
|
|
|
|
app.get(
|
|
'/api/get_main_block_details/:id',
|
|
exceptionHandler(async (req, res) => {
|
|
let id = req.params.id
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: api,
|
|
data: {
|
|
method: 'get_main_block_details',
|
|
params: {
|
|
id: id
|
|
}
|
|
}
|
|
})
|
|
res.json(response.data)
|
|
})
|
|
)
|
|
|
|
const getWhitelistedAssets = async (offset, count, searchText) => {
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: config.assets_whitelist_url || 'https://api.zano.org/assets_whitelist_testnet.json'
|
|
});
|
|
|
|
if (!response.data.assets) {
|
|
throw new Error('Assets whitelist response not correct');
|
|
}
|
|
|
|
const allAssets = response.data.assets;
|
|
allAssets.unshift({
|
|
asset_id: "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a",
|
|
logo: "",
|
|
price_url: "",
|
|
ticker: "ZANO",
|
|
full_name: "Zano (Native)",
|
|
total_max_supply: "0",
|
|
current_supply: "0",
|
|
decimal_point: 0,
|
|
meta_info: "",
|
|
price: 0
|
|
});
|
|
const searchTextLower = searchText?.toLowerCase();
|
|
|
|
const filteredAssets = allAssets
|
|
.filter(asset => {
|
|
return searchText
|
|
? (
|
|
asset.ticker?.toLowerCase()?.includes(searchTextLower) ||
|
|
asset.full_name?.toLowerCase()?.includes(searchTextLower)
|
|
)
|
|
: true
|
|
});
|
|
|
|
if (filteredAssets.length > 0) {
|
|
return filteredAssets.slice(offset, offset + count);
|
|
} else {
|
|
return allAssets.filter(e => e.asset_id === searchText).slice(offset, offset + count);
|
|
}
|
|
|
|
}
|
|
|
|
app.get(
|
|
'/api/get_whitelisted_assets/:offset/:count',
|
|
exceptionHandler(async (req, res) => {
|
|
const offset = parseInt(req.params.offset, 10);
|
|
const count = parseInt(req.params.count, 10);
|
|
const searchText = req.query.search || '';
|
|
|
|
res.send(await getWhitelistedAssets(offset, count, searchText));
|
|
|
|
})
|
|
)
|
|
|
|
app.get(
|
|
'/api/get_assets/:offset/:count',
|
|
exceptionHandler(async (req, res) => {
|
|
const offset = parseInt(req.params.offset, 10);
|
|
const count = parseInt(req.params.count, 10);
|
|
const searchText = req.query.search || '';
|
|
|
|
if (!searchText) {
|
|
const rows = (
|
|
await db.query(
|
|
"SELECT * FROM assets ORDER BY id ASC LIMIT $1 OFFSET $2",
|
|
[
|
|
count,
|
|
offset
|
|
]
|
|
)
|
|
).rows;
|
|
|
|
return res.send(rows);
|
|
}
|
|
|
|
const firstSearchRowCount = (await db.query(
|
|
`SELECT COUNT(*) FROM assets WHERE
|
|
LOWER(ticker) LIKE CONCAT('%', LOWER($1::text), '%') OR
|
|
LOWER(full_name) LIKE CONCAT('%', LOWER($1::text), '%')`,
|
|
[
|
|
searchText
|
|
]
|
|
)).rows[0].count;
|
|
|
|
|
|
if (firstSearchRowCount > 0) {
|
|
const rows = (
|
|
await db.query(
|
|
`SELECT * FROM assets WHERE
|
|
LOWER(ticker) LIKE CONCAT('%', LOWER($3::text), '%') OR
|
|
LOWER(full_name) LIKE CONCAT('%', LOWER($3::text), '%')
|
|
ORDER BY id ASC
|
|
LIMIT $1 OFFSET $2`,
|
|
[
|
|
count,
|
|
offset,
|
|
searchText
|
|
]
|
|
)
|
|
).rows;
|
|
|
|
return res.send(rows);
|
|
} else {
|
|
const rows = (
|
|
await db.query(
|
|
"SELECT * FROM assets WHERE asset_id=$3 LIMIT $1 OFFSET $2",
|
|
[
|
|
count,
|
|
offset,
|
|
searchText
|
|
]
|
|
)
|
|
).rows;
|
|
|
|
return res.send(rows);
|
|
}
|
|
})
|
|
)
|
|
|
|
let priceData = {};
|
|
|
|
app.get('/api/price', exceptionHandler(async (req, res) => {
|
|
|
|
if (req.query.asset_id) {
|
|
|
|
if (req.query.asset_id === ZANO_ASSET_ID) {
|
|
|
|
if (!priceData?.zano?.zano?.usd) {
|
|
res.send({ success: false, data: "Price not found" });
|
|
}
|
|
|
|
return res.send({
|
|
success: true,
|
|
data: {
|
|
name: "Zano",
|
|
usd: priceData?.zano?.zano?.usd,
|
|
usd_24h_change: priceData?.zano?.zano?.usd_24h_change
|
|
}
|
|
})
|
|
}
|
|
|
|
const assetData = (await db.query("SELECT * FROM assets WHERE asset_id = $1", [req.query.asset_id]))?.rows?.[0];
|
|
|
|
if (!assetData) {
|
|
return res.json({ success: false, data: "Asset not found" });
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
const responseData = {
|
|
success: true,
|
|
data: priceData.zano
|
|
};
|
|
|
|
switch (req.query.asset) {
|
|
case "ethereum":
|
|
if (priceData?.ethereum?.ethereum?.usd === undefined) {
|
|
responseData.data = {};
|
|
responseData.success = false;
|
|
} else {
|
|
responseData.data = priceData.ethereum;
|
|
}
|
|
break;
|
|
default:
|
|
if (priceData?.zano?.zano?.usd === undefined) {
|
|
responseData.data = {};
|
|
responseData.success = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return res.json(responseData);
|
|
|
|
}));
|
|
|
|
app.get('/api/get_asset_details/:asset_id', exceptionHandler(async (req, res) => {
|
|
const { asset_id } = req.params;
|
|
const dbAsset = (await db.query("SELECT * FROM assets WHERE asset_id = $1", [asset_id])).rows[0];
|
|
if (!dbAsset) {
|
|
|
|
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: config.assets_whitelist_url || 'https://api.zano.org/assets_whitelist_testnet.json'
|
|
});
|
|
|
|
if (!response.data.assets) {
|
|
throw new Error('Assets whitelist response not correct');
|
|
}
|
|
|
|
const allAssets = response.data.assets;
|
|
allAssets.unshift({
|
|
asset_id: "d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a",
|
|
logo: "",
|
|
price_url: "",
|
|
ticker: "ZANO",
|
|
full_name: "Zano (Native)",
|
|
total_max_supply: "0",
|
|
current_supply: "0",
|
|
decimal_point: 0,
|
|
meta_info: "",
|
|
price: 0
|
|
});
|
|
|
|
const whitelistedAsset = allAssets.find(e => e.asset_id === asset_id);
|
|
|
|
if (whitelistedAsset) {
|
|
return res.json({ success: true, asset: whitelistedAsset });
|
|
} else {
|
|
return res.json({ success: false, data: "Asset not found" });
|
|
}
|
|
} else {
|
|
return res.json({ success: true, asset: dbAsset });
|
|
}
|
|
}));
|
|
|
|
(async () => {
|
|
while (true) {
|
|
try {
|
|
|
|
async function fetchAssets(offset, count) {
|
|
try {
|
|
const response = await axios({
|
|
method: 'get',
|
|
url: api,
|
|
data: {
|
|
method: 'get_assets_list',
|
|
params: {
|
|
count: count,
|
|
offset: offset,
|
|
}
|
|
}
|
|
});
|
|
|
|
return response?.data?.result?.assets || [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const zanoInfo = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=zano&vs_currencies=usd&include_24hr_change=true").then(res => res.json());
|
|
|
|
await new Promise(res => setTimeout(res, 5 * 1e3));
|
|
|
|
try {
|
|
const ethInfo = await fetch("https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd&include_24hr_change=true").then(res => res.json());
|
|
console.log('ETH INFO: ', ethInfo);
|
|
if (ethInfo?.ethereum?.usd !== undefined) {
|
|
priceData.ethereum = ethInfo;
|
|
}
|
|
} catch (error) {
|
|
console.log('ETH PARSING ERROR');
|
|
console.log('Error: ', error);
|
|
}
|
|
|
|
console.log('ZANO INFO: ', zanoInfo);
|
|
|
|
if (zanoInfo?.zano?.usd !== undefined) {
|
|
priceData.zano = zanoInfo;
|
|
}
|
|
|
|
const assets = [];
|
|
|
|
let iterator = 0;
|
|
const amountPerIteration = 100;
|
|
|
|
while (true) {
|
|
const newAssets = await fetchAssets(iterator + 1, iterator + amountPerIteration);
|
|
if (!newAssets.length) break;
|
|
assets.push(...newAssets);
|
|
iterator += amountPerIteration;
|
|
}
|
|
|
|
|
|
console.log('Got assets list');
|
|
|
|
const assetsRows = (await db.query("SELECT * FROM assets")).rows;
|
|
for (const assetRow of assetsRows) {
|
|
const foundAsset = assets.find(e => e.asset_id === assetRow.asset_id);
|
|
if (!foundAsset) {
|
|
await db.query("DELETE FROM assets WHERE asset_id=$1", [assetRow.asset_id]);
|
|
} else {
|
|
const {
|
|
asset_id,
|
|
logo,
|
|
price_url,
|
|
ticker,
|
|
full_name,
|
|
total_max_supply,
|
|
current_supply,
|
|
decimal_point,
|
|
meta_info,
|
|
price
|
|
} = foundAsset;
|
|
|
|
await db.query(
|
|
`UPDATE assets SET
|
|
logo=$1,
|
|
price_url=$2,
|
|
ticker=$3,
|
|
full_name=$4,
|
|
total_max_supply=$5,
|
|
current_supply=$6,
|
|
decimal_point=$7,
|
|
meta_info=$8,
|
|
price=$9 WHERE asset_id=$10
|
|
`,
|
|
[
|
|
logo || "",
|
|
price_url || "",
|
|
ticker || "",
|
|
full_name || "",
|
|
total_max_supply?.toString() || "0",
|
|
current_supply?.toString() || "0",
|
|
decimal_point || 0,
|
|
meta_info || "",
|
|
price,
|
|
asset_id
|
|
]
|
|
)
|
|
}
|
|
}
|
|
for (const asset of assets) {
|
|
const foundAsset = assetsRows.find(e => e.asset_id === asset.asset_id);
|
|
if (!foundAsset && asset.asset_id) {
|
|
const {
|
|
asset_id,
|
|
logo,
|
|
price_url,
|
|
ticker,
|
|
full_name,
|
|
total_max_supply,
|
|
current_supply,
|
|
decimal_point,
|
|
meta_info,
|
|
price = 0
|
|
} = asset;
|
|
|
|
await db.query(
|
|
`
|
|
INSERT INTO assets(
|
|
asset_id,
|
|
logo,
|
|
price_url,
|
|
ticker,
|
|
full_name,
|
|
total_max_supply,
|
|
current_supply,
|
|
decimal_point,
|
|
meta_info,
|
|
price
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
`,
|
|
[
|
|
asset_id,
|
|
logo || "",
|
|
price_url || "",
|
|
ticker,
|
|
full_name,
|
|
total_max_supply?.toString(),
|
|
current_supply?.toString(),
|
|
decimal_point,
|
|
meta_info || "",
|
|
price
|
|
]
|
|
)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log('ASSETS PARSING ERROR');
|
|
console.log('Error: ', error);
|
|
}
|
|
await new Promise(res => setTimeout(res, 60 * 1e3));
|
|
}
|
|
})();
|