diff --git a/packages/api/.env b/packages/api/.env index f784f739d..414941241 100644 --- a/packages/api/.env +++ b/packages/api/.env @@ -10,3 +10,4 @@ DASHCORE_PASS=password EPOCH_CHANGE_TIME=3600000 DAPI_URL=127.0.0.1:1443:self-signed TCP_CONNECT_TIMEOUT=400 +DPNS_CONTRACT=5mjGWa9mruHnLBht3ntbfgodcSoJxA1XIfYiv1PFMVU= diff --git a/packages/api/README.md b/packages/api/README.md index f7b839ec2..28a0013d4 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -412,7 +412,11 @@ GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEE type: 0, gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: { + identifier: "6q9RFbeea73tE31LGMBLFZhtBUX3wZL3TcNynqE18Zgs", + aliases: [] + } } ``` @@ -450,7 +454,11 @@ GET /transactions?=1&limit=10&order=asc type: 0, gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: { + identifier: "6q9RFbeea73tE31LGMBLFZhtBUX3wZL3TcNynqE18Zgs", + aliases: [] + } }, ... ] } @@ -768,7 +776,8 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1 timestamp: "2024-03-18T10:13:54.150Z", gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec" }, ... ] } diff --git a/packages/api/package.json b/packages/api/package.json index b90cd4c84..e5c8ecb71 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,19 +12,19 @@ "lint": "standard ." }, "dependencies": { - "@dashevo/dapi-client": "1.5.1", "@dashevo/dashd-rpc": "19.0.0", "@fastify/cors": "^8.3.0", "@scure/base": "^1.1.5", "bs58": "^6.0.0", "cbor": "^9.0.2", - "dash": "4.5.1", + "dash": "4.4.1", "dotenv": "^16.3.1", "fastify": "^4.21.0", "fastify-metrics": "^11.0.0", "knex": "^2.5.1", "node-fetch": "^2.6.11", - "pg": "^8.11.3" + "pg": "^8.11.3", + "@dashevo/dapi-client": "github:owl352/dapi-client" }, "devDependencies": { "standard": "^17.1.0", diff --git a/packages/api/src/DAPI.js b/packages/api/src/DAPI.js index a2fc9ba64..9f5d02bd9 100644 --- a/packages/api/src/DAPI.js +++ b/packages/api/src/DAPI.js @@ -23,6 +23,27 @@ class DAPI { const { epochsInfo } = await this.dapi.platform.getEpochsInfo(start, count, { ascending }) return epochsInfo } + + async getContestedState (contractId, + documentTypeName, + indexName = 'parentNameAndLabel', + resultType = 2, + indexValuesList, startAtIdentifierInfo, + allowIncludeLockedAndAbstainingVoteTally, + count + ) { + const { contestedResourceContenders } = await this.dapi.platform.getContestedResourceVoteState( + Buffer.from(contractId, 'base64'), + documentTypeName, + indexName, + resultType, + indexValuesList, + startAtIdentifierInfo, + allowIncludeLockedAndAbstainingVoteTally, + count + ) + return contestedResourceContenders + } } module.exports = DAPI diff --git a/packages/api/src/constants.js b/packages/api/src/constants.js index 729ac0a23..72f606188 100644 --- a/packages/api/src/constants.js +++ b/packages/api/src/constants.js @@ -5,6 +5,7 @@ let genesisTime module.exports = { EPOCH_CHANGE_TIME: Number(process.env.EPOCH_CHANGE_TIME), TCP_CONNECT_TIMEOUT: Number(process.env.TCP_CONNECT_TIMEOUT), + DPNS_CONTRACT: process.env.DPNS_CONTRACT ?? '5mjGWa9mruHnLBht3ntbfgodcSoJxA1XIfYiv1PFMVU=', get genesisTime () { if (!genesisTime || isNaN(genesisTime)) { return TenderdashRPC.getBlockByHeight(1).then((blockInfo) => { diff --git a/packages/api/src/controllers/IdentitiesController.js b/packages/api/src/controllers/IdentitiesController.js index 0e6092fe1..33cc6ae63 100644 --- a/packages/api/src/controllers/IdentitiesController.js +++ b/packages/api/src/controllers/IdentitiesController.js @@ -1,5 +1,8 @@ const IdentitiesDAO = require('../dao/IdentitiesDAO') const { IDENTITY_CREDIT_WITHDRAWAL } = require('../enums/StateTransitionEnum') +const { validateAliases } = require('../utils') +const Identity = require('../models/Identity') +const { base58 } = require('@scure/base') class IdentitiesController { constructor (knex, dapi) { @@ -16,15 +19,44 @@ class IdentitiesController { return response.status(404).send({ message: 'not found' }) } + const validatedAliases = await validateAliases(identity.aliases, identity.identifier, this.dapi) + const balance = await this.dapi.getIdentityBalance(identifier) - response.send({ ...identity, balance }) + response.send(Identity.fromObject({ ...identity, aliases: validatedAliases, balance })) } getIdentityByDPNS = async (request, response) => { const { dpns } = request.query - const identity = await this.identitiesDAO.getIdentityByDPNS(dpns) + let preIdentity + let identity + + if (!dpns.includes('.')) { + preIdentity = await this.identitiesDAO.getIdentityByDPNS(dpns) + + if (!preIdentity) { + return response.status(404).send({ message: 'not found' }) + } + } + + const [{ contestedState }] = await validateAliases( + [preIdentity ? preIdentity.aliases.find(v => v.includes(`${dpns}.`)) : dpns], + null, + this.dapi + ) + + if (contestedState) { + if (typeof contestedState.finishedVoteInfo?.wonByIdentityId === 'string') { + const identifier = base58.encode(Buffer.from(contestedState.finishedVoteInfo?.wonByIdentityId, 'base64')) + + identity = await this.identitiesDAO.getIdentityByIdentifier(identifier) + } + } + + if (!contestedState) { + identity = preIdentity ?? await this.identitiesDAO.getIdentityByDPNS(dpns) + } if (!identity) { return response.status(404).send({ message: 'not found' }) @@ -32,6 +64,10 @@ class IdentitiesController { const balance = await this.dapi.getIdentityBalance(identity.identifier) + const validatedAliases = await validateAliases(identity.aliases, identity.identifier, this.dapi) + + identity = Identity.fromObject({ ...identity, aliases: validatedAliases }) + response.send({ ...identity, balance }) } @@ -42,7 +78,10 @@ class IdentitiesController { const identitiesWithBalance = await Promise.all(identities.resultSet.map(async identity => { const balance = await this.dapi.getIdentityBalance(identity.identifier) - return { ...identity, balance } + + const validatedAliases = await validateAliases(identity.aliases, identity.identifier, this.dapi) + + return { ...identity, aliases: validatedAliases, balance } })) response.send({ ...identities, resultSet: identitiesWithBalance }) diff --git a/packages/api/src/controllers/MainController.js b/packages/api/src/controllers/MainController.js index bbe3b6590..f98d77e59 100644 --- a/packages/api/src/controllers/MainController.js +++ b/packages/api/src/controllers/MainController.js @@ -6,6 +6,8 @@ const IdentitiesDAO = require('../dao/IdentitiesDAO') const ValidatorsDAO = require('../dao/ValidatorsDAO') const TenderdashRPC = require('../tenderdashRpc') const Epoch = require('../models/Epoch') +const { validateAliases } = require('../utils') +const { base58 } = require('@scure/base') const API_VERSION = require('../../package.json').version const PLATFORM_VERSION = '1' + require('../../package.json').dependencies.dash.substring(1) @@ -115,9 +117,9 @@ class MainController { const identity = await this.identitiesDAO.getIdentityByIdentifier(query) if (identity) { - const balance = await this.dapi.getIdentityBalance(identity.identifier) - - return response.send({ identity: { ...identity, balance } }) + // Sending without actual balance and aliases, because on frontend we were making + // request /identity/:identifier for actual data + return response.send({ identity }) } // search data contracts @@ -136,12 +138,39 @@ class MainController { } if (/^[^\s.]+(\.[^\s.]+)*$/.test(query)) { - const identity = await this.identitiesDAO.getIdentityByDPNS(query) + let preIdentity + let identity - if (identity) { - const balance = await this.dapi.getIdentityBalance(identity.identifier) + if (!query.includes('.')) { + preIdentity = await this.identitiesDAO.getIdentityByDPNS(query) + + if (!preIdentity) { + return response.status(404).send({ message: 'not found' }) + } + } + + const [{ contestedState }] = await validateAliases( + [preIdentity ? preIdentity.aliases.find(v => v.includes(`${query}.`)) : query], + null, + this.dapi + ) + + if (contestedState) { + if (typeof contestedState.finishedVoteInfo?.wonByIdentityId === 'string') { + const identifier = base58.encode(Buffer.from(contestedState.finishedVoteInfo?.wonByIdentityId, 'base64')) - return response.send({ identity: { ...identity, balance } }) + identity = await this.identitiesDAO.getIdentityByIdentifier(identifier) + } + } + + if (!contestedState) { + identity = preIdentity ?? await this.identitiesDAO.getIdentityByDPNS(query) + } + + if (identity) { + // Sending without actual balance and aliases, because on frontend we were making + // request /identity/:identifier for actual data + return response.send({ identity }) } } diff --git a/packages/api/src/controllers/TransactionsController.js b/packages/api/src/controllers/TransactionsController.js index c792866c6..da88ffa0e 100644 --- a/packages/api/src/controllers/TransactionsController.js +++ b/packages/api/src/controllers/TransactionsController.js @@ -1,11 +1,12 @@ const TransactionsDAO = require('../dao/TransactionsDAO') const utils = require('../utils') -const { calculateInterval } = require('../utils') +const { calculateInterval, validateAliases } = require('../utils') class TransactionsController { - constructor (client, knex) { + constructor (client, knex, dapi) { this.client = client this.transactionsDAO = new TransactionsDAO(knex) + this.dapi = dapi } getTransactionByHash = async (request, reply) => { @@ -17,7 +18,17 @@ class TransactionsController { return reply.status(404).send({ message: 'not found' }) } - reply.send(transaction) + const validatedAliases = transaction.owner.aliases?.length > 0 + ? await validateAliases(transaction.owner.aliases ?? [], transaction.owner?.identifier, this.dapi) + : [] + + reply.send({ + ...transaction, + owner: { + ...transaction.owner, + aliases: validatedAliases + } + }) } getTransactions = async (request, response) => { @@ -29,7 +40,19 @@ class TransactionsController { const transactions = await this.transactionsDAO.getTransactions(Number(page ?? 1), Number(limit ?? 10), order) - response.send(transactions) + const transactionsWithCorrectAliases = await Promise.all(transactions.resultSet.map(async transaction => + ({ + ...transaction, + owner: { + ...transaction.owner, + aliases: transaction.owner.aliases?.length > 0 + ? await validateAliases(transaction.owner.aliases ?? [], transaction.owner?.identifier, this.dapi) + : [] + } + }) + )) + + response.send({ ...transactions, resultSet: transactionsWithCorrectAliases }) } getTransactionHistory = async (request, response) => { diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js index 5ce47094d..020e1a772 100644 --- a/packages/api/src/dao/IdentitiesDAO.js +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -246,14 +246,15 @@ module.exports = class IdentitiesDAO { const subquery = this.knex('state_transitions') .select('state_transitions.id as state_transition_id', 'state_transitions.hash as tx_hash', 'state_transitions.index as index', 'state_transitions.type as type', 'state_transitions.block_hash as block_hash', - 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error' + 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error', + 'state_transitions.owner as owner' ) .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) .where('state_transitions.owner', '=', identifier) const rows = await this.knex.with('with_alias', subquery) .select('state_transition_id', 'tx_hash', 'index', 'block_hash', 'type', 'rank', - 'gas_used', 'status', 'gas_used', + 'gas_used', 'status', 'gas_used', 'owner', 'blocks.timestamp as timestamp', 'blocks.height as block_height') .select(this.knex('with_alias').count('*').as('total_count')) .leftJoin('blocks', 'blocks.hash', 'block_hash') diff --git a/packages/api/src/dao/TransactionsDAO.js b/packages/api/src/dao/TransactionsDAO.js index b5d0e87ea..156d48a66 100644 --- a/packages/api/src/dao/TransactionsDAO.js +++ b/packages/api/src/dao/TransactionsDAO.js @@ -8,13 +8,23 @@ module.exports = class TransactionsDAO { } getTransactionByHash = async (hash) => { + const aliasesSubquery = this.knex('identity_aliases') + .select('identity_identifier', this.knex.raw('array_agg(alias) as aliases')) + .groupBy('identity_identifier') + .as('aliases') + const [row] = await this.knex('state_transitions') - .select('state_transitions.hash as tx_hash', 'state_transitions.data as data', - 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error', - 'state_transitions.type as type', 'state_transitions.index as index', 'blocks.height as block_height', - 'blocks.hash as block_hash', 'blocks.timestamp as timestamp') + .select( + 'state_transitions.hash as tx_hash', 'state_transitions.data as data', + 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', + 'state_transitions.error as error', 'state_transitions.type as type', + 'state_transitions.index as index', 'blocks.height as block_height', + 'blocks.hash as block_hash', 'blocks.timestamp as timestamp', 'state_transitions.owner as owner', + 'aliases.aliases as aliases' + ) .whereILike('state_transitions.hash', hash) .leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash') + .leftJoin(aliasesSubquery, 'aliases.identity_identifier', 'owner') if (!row) { return null @@ -27,17 +37,24 @@ module.exports = class TransactionsDAO { const fromRank = ((page - 1) * limit) + 1 const toRank = fromRank + limit - 1 + const aliasesSubquery = this.knex('identity_aliases') + .select('identity_identifier', this.knex.raw('array_agg(alias) as aliases')) + .groupBy('identity_identifier') + .as('aliases') + const subquery = this.knex('state_transitions') .select(this.knex('state_transitions').count('hash').as('total_count'), 'state_transitions.hash as tx_hash', 'state_transitions.data as data', 'state_transitions.type as type', 'state_transitions.index as index', 'state_transitions.gas_used as gas_used', 'state_transitions.status as status', 'state_transitions.error as error', - 'state_transitions.block_hash as block_hash', 'state_transitions.id as id') + 'state_transitions.block_hash as block_hash', 'state_transitions.id as id', 'state_transitions.owner as owner') .select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`)) + .select('aliases') + .leftJoin(aliasesSubquery, 'aliases.identity_identifier', 'state_transitions.owner') .as('state_transitions') const rows = await this.knex(subquery) .select('total_count', 'data', 'type', 'index', 'rank', 'block_hash', 'state_transitions.tx_hash as tx_hash', - 'gas_used', 'status', 'error', 'blocks.height as block_height', 'blocks.timestamp as timestamp') + 'gas_used', 'status', 'error', 'blocks.height as block_height', 'blocks.timestamp as timestamp', 'owner', 'aliases') .leftJoin('blocks', 'blocks.hash', 'block_hash') .whereBetween('rank', [fromRank, toRank]) .orderBy('state_transitions.id', order) diff --git a/packages/api/src/models/Identity.js b/packages/api/src/models/Identity.js index ddfd01e6d..229fc2fb3 100644 --- a/packages/api/src/models/Identity.js +++ b/packages/api/src/models/Identity.js @@ -30,4 +30,8 @@ module.exports = class Identity { static fromRow ({ identifier, owner, revision, balance, timestamp, total_txs, total_data_contracts, total_documents, total_transfers, tx_hash, is_system, aliases }) { return new Identity(identifier, owner, revision, Number(balance), timestamp, Number(total_txs), Number(total_data_contracts), Number(total_documents), Number(total_transfers), tx_hash, is_system, aliases) } + + static fromObject ({ identifier, owner, revision, balance, timestamp, totalTxs, totalDataContracts, totalDocuments, totalTransfers, txHash, isSystem, aliases }) { + return new Identity(identifier, owner, revision, balance, timestamp, totalTxs, totalDataContracts, totalDocuments, totalTransfers, txHash, isSystem, aliases) + } } diff --git a/packages/api/src/models/Transaction.js b/packages/api/src/models/Transaction.js index bf062ff2e..0beba1206 100644 --- a/packages/api/src/models/Transaction.js +++ b/packages/api/src/models/Transaction.js @@ -13,8 +13,9 @@ module.exports = class Transaction { gasUsed status error + owner - constructor (hash, index, blockHash, blockHeight, type, data, timestamp, gasUsed, status, error) { + constructor (hash, index, blockHash, blockHeight, type, data, timestamp, gasUsed, status, error, owner) { this.hash = hash ?? null this.index = index ?? null this.blockHash = blockHash ?? null @@ -25,10 +26,24 @@ module.exports = class Transaction { this.gasUsed = gasUsed ?? null this.status = status ?? null this.error = error ?? null + this.owner = owner || null } - // eslint-disable-next-line camelcase - static fromRow ({ tx_hash, index, block_hash, block_height, type, data, timestamp, gas_used, status, error }) { + /* eslint-disable camelcase */ + static fromRow ({ + tx_hash, + index, + block_hash, + block_height, + type, + data, + timestamp, + gas_used, + status, + error, + owner, + aliases + }) { let decodedError = null try { @@ -41,6 +56,16 @@ module.exports = class Transaction { decodedError = 'Cannot deserialize' } - return new Transaction(tx_hash, index, block_hash, block_height, type, data, timestamp, parseInt(gas_used), status, decodedError ?? error) + return new Transaction( + tx_hash, index, block_hash, + block_height, type, data, + timestamp, parseInt(gas_used), + status, decodedError ?? error, + aliases + ? { + identifier: owner?.trim(), + aliases + } + : owner?.trim()) } } diff --git a/packages/api/src/server.js b/packages/api/src/server.js index 943318896..25c499f84 100644 --- a/packages/api/src/server.js +++ b/packages/api/src/server.js @@ -16,7 +16,6 @@ const ValidatorsController = require('./controllers/ValidatorsController') const { getKnex } = require('./utils') const BlocksDAO = require('./dao/BlocksDAO') const DAPI = require('./DAPI') -const DAPIClient = require('@dashevo/dapi-client') const RateController = require('./controllers/RateController') const { default: loadWasmDpp } = require('dash').PlatformProtocol @@ -45,16 +44,16 @@ let dapi module.exports = { start: async () => { - client = new Dash.Client() + client = new Dash.Client({ + dapiAddresses: (process.env.DAPI_URL ?? '127.0.0.1:1443:self-signed').split(','), + network: process.env.NETWORK ?? 'testnet' + }) await loadWasmDpp() await client.platform.initialize() - const dapiClient = new DAPIClient({ - dapiAddresses: (process.env.DAPI_URL ?? '127.0.0.1:1443:self-signed').split(','), - network: process.env.NETWORK ?? 'testnet' - }) + const dapiClient = client.getDAPIClient() const { dpp } = client.platform @@ -79,7 +78,7 @@ module.exports = { const mainController = new MainController(knex, dapi) const epochController = new EpochController(knex, dapi) const blocksController = new BlocksController(knex) - const transactionsController = new TransactionsController(client, knex) + const transactionsController = new TransactionsController(client, knex, dapi) const dataContractsController = new DataContractsController(knex) const documentsController = new DocumentsController(knex) const identitiesController = new IdentitiesController(knex, dapi) diff --git a/packages/api/src/utils.js b/packages/api/src/utils.js index f07c5a48c..41df8978e 100644 --- a/packages/api/src/utils.js +++ b/packages/api/src/utils.js @@ -1,7 +1,8 @@ const crypto = require('crypto') const StateTransitionEnum = require('./enums/StateTransitionEnum') const net = require('net') -const { TCP_CONNECT_TIMEOUT } = require('./constants') +const { TCP_CONNECT_TIMEOUT, DPNS_CONTRACT } = require('./constants') +const { base58 } = require('@scure/base') const getKnex = () => { return require('knex')({ @@ -174,4 +175,63 @@ const calculateInterval = (start, end) => { }, intervalsInRFC[0]) } -module.exports = { hash, decodeStateTransition, getKnex, checkTcpConnect, calculateInterval } +const getLabelBuffer = (text) => + Buffer.from(`${ + Buffer.from(`12${`0${(text.length).toString(16)}`.slice(-2) + }`, 'hex')}${text}` + ) + +const validateAliases = async (aliases, identifier, dapi) => { + const aliasesWithContestedState = await Promise.all(aliases.map(async (alias) => { + const [label, domain] = alias.split('.') + + const normalizedLabel = label.toLowerCase().replace(/[oli]/g, (match) => { + if (match === 'o') { + return '0' + } + if (match === 'l' || match === 'i') { + return '1' + } + return match + }) + + if (/^[a-zA-Z01]{3,19}$/.test(normalizedLabel)) { + const domainBuffer = getLabelBuffer(domain) + const labelBuffer = getLabelBuffer(normalizedLabel) + + const contestedState = await dapi.getContestedState( + DPNS_CONTRACT, + 'domain', + 'parentNameAndLabel', + 1, + [ + domainBuffer, + labelBuffer + ] + ) + + return { alias, contestedState } + } + + return { alias, contestedState: null } + })) + + return (identifier + ? aliasesWithContestedState.filter(alias => ( + typeof alias.contestedState?.finishedVoteInfo?.wonByIdentityId === 'string' + ? base58.encode(Buffer.from(alias.contestedState?.finishedVoteInfo.wonByIdentityId, 'base64')) === identifier + : false + ) || alias.contestedState === null + ).map(v => v.alias) + : aliasesWithContestedState) +} + +module.exports = { + hash, + decodeStateTransition, + getKnex, + checkTcpConnect, + calculateInterval, + validateAliases, + getLabelBuffer +} diff --git a/packages/api/test/integration/identities.spec.js b/packages/api/test/integration/identities.spec.js index abbb4c1e1..cb231f377 100644 --- a/packages/api/test/integration/identities.spec.js +++ b/packages/api/test/integration/identities.spec.js @@ -30,6 +30,8 @@ describe('Identities routes', () => { before(async () => { mock.method(DAPI.prototype, 'getIdentityBalance', async () => 0) + mock.method(DAPI.prototype, 'getContestedState', async () => null) + mock.method(tenderdashRpc, 'getBlockByHeight', async () => ({ block: { header: { @@ -58,7 +60,7 @@ describe('Identities routes', () => { const identity = await fixtures.identity(knex, { block_hash: block.hash }) const { alias } = await fixtures.identity_alias(knex, { - alias: 'test', + alias: 'test.dash', identity } ) @@ -886,7 +888,8 @@ describe('Identities routes', () => { timestamp: _transaction.block.timestamp.toISOString(), gasUsed: _transaction.transaction.gas_used, status: _transaction.transaction.status, - error: _transaction.transaction.error + error: _transaction.transaction.error, + owner: _transaction.transaction.owner })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -929,7 +932,8 @@ describe('Identities routes', () => { timestamp: _transaction.block.timestamp.toISOString(), gasUsed: _transaction.transaction.gas_used, status: _transaction.transaction.status, - error: _transaction.transaction.error + error: _transaction.transaction.error, + owner: _transaction.transaction.owner })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -972,7 +976,8 @@ describe('Identities routes', () => { timestamp: _transaction.block.timestamp.toISOString(), gasUsed: _transaction.transaction.gas_used, status: _transaction.transaction.status, - error: _transaction.transaction.error + error: _transaction.transaction.error, + owner: _transaction.transaction.owner })) assert.deepEqual(body.resultSet, expectedTransactions) @@ -1015,7 +1020,8 @@ describe('Identities routes', () => { timestamp: _transaction.block.timestamp.toISOString(), gasUsed: _transaction.transaction.gas_used, status: _transaction.transaction.status, - error: _transaction.transaction.error + error: _transaction.transaction.error, + owner: _transaction.transaction.owner })) assert.deepEqual(body.resultSet, expectedTransactions) diff --git a/packages/api/test/integration/main.spec.js b/packages/api/test/integration/main.spec.js index 6600ffeba..cb29c311b 100644 --- a/packages/api/test/integration/main.spec.js +++ b/packages/api/test/integration/main.spec.js @@ -39,6 +39,8 @@ describe('Other routes', () => { nextEpoch: 0 }]) + mock.method(DAPI.prototype, 'getContestedState', async () => null) + mock.method(tenderdashRpc, 'getBlockByHeight', async () => ({ block: { header: { @@ -117,15 +119,19 @@ describe('Other routes', () => { for (let i = 0; i < 48; i++) { const tmpBlock = await fixtures.block(knex, { - timestamp: new Date(new Date().getTime() - 3600000 * i) + timestamp: new Date(new Date().getTime() - 3600000 * i), + height: i + 10 }) + const transaction = await fixtures.transaction(knex, { block_hash: tmpBlock.hash, type: 0, owner: identity.identifier, gas_used: 10000 }) + transactions.push(transaction.hash) + blocks.push(tmpBlock) } }) @@ -171,7 +177,11 @@ describe('Other routes', () => { timestamp: block.timestamp.toISOString(), gasUsed: dataContractTransaction.gas_used, status: dataContractTransaction.status, - error: dataContractTransaction.error + error: dataContractTransaction.error, + owner: { + identifier: dataContractTransaction.owner, + aliases: [identityAlias.alias] + } } assert.deepEqual({ transaction: expectedTransaction }, body) @@ -192,7 +202,7 @@ describe('Other routes', () => { l1LockedHeight: block.l1_locked_height, validator: block.validator }, - txs: transactions + txs: [identityTransaction.hash, dataContractTransaction.hash, documentTransaction.hash] } assert.deepEqual({ block: expectedBlock }, body) @@ -228,7 +238,7 @@ describe('Other routes', () => { const expectedIdentity = { identifier: identity.identifier, revision: 0, - balance: 0, + balance: null, timestamp: block.timestamp.toISOString(), txHash: identityTransaction.hash, totalTxs: 51, @@ -251,7 +261,7 @@ describe('Other routes', () => { const expectedIdentity = { identifier: identity.identifier, revision: 0, - balance: 0, + balance: null, timestamp: block.timestamp.toISOString(), txHash: identityTransaction.hash, totalTxs: 51, @@ -320,7 +330,7 @@ describe('Other routes', () => { api: { version: require('../../package.json').version, block: { - height: 10, + height: blocks.length - 1, hash: blocks[blocks.length - 1].hash, timestamp: blocks[blocks.length - 1].timestamp.toISOString() } diff --git a/packages/api/test/integration/transactions.spec.js b/packages/api/test/integration/transactions.spec.js index 672d412f5..4b01ab46c 100644 --- a/packages/api/test/integration/transactions.spec.js +++ b/packages/api/test/integration/transactions.spec.js @@ -6,6 +6,7 @@ const { getKnex } = require('../../src/utils') const fixtures = require('../utils/fixtures') const StateTransitionEnum = require('../../src/enums/StateTransitionEnum') const tenderdashRpc = require('../../src/tenderdashRpc') +const DAPI = require('../../src/DAPI') describe('Transaction routes', () => { let app @@ -13,6 +14,7 @@ describe('Transaction routes', () => { let knex let identity + let identityAlias let block let transactions @@ -25,6 +27,8 @@ describe('Transaction routes', () => { } })) + mock.method(DAPI.prototype, 'getContestedState', async () => null) + const startDate = new Date(new Date() - 1000 * 60 * 60) app = await server.start() @@ -38,6 +42,8 @@ describe('Transaction routes', () => { }) identity = await fixtures.identity(knex, { block_hash: block.hash }) + identityAlias = await fixtures.identity_alias(knex, { alias: 'test.dash', identity }) + transactions = [{ transaction: identity.transaction, block }] // error tx @@ -104,7 +110,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: transaction.transaction.gas_used, status: transaction.transaction.status, - error: transaction.transaction.error + error: transaction.transaction.error, + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } } assert.deepEqual(expectedTransaction, body) @@ -126,7 +136,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: 0, status: 'FAIL', - error: 'Cannot deserialize' + error: 'Cannot deserialize', + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } } assert.deepEqual(expectedTransaction, body) @@ -162,7 +176,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: transaction.transaction.gas_used, status: transaction.transaction.status, - error: transaction.transaction.error + error: transaction.transaction.error, + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -191,7 +209,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: transaction.transaction.gas_used, status: transaction.transaction.status, - error: transaction.transaction.error + error: transaction.transaction.error, + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -220,7 +242,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: transaction.transaction.gas_used, status: transaction.transaction.status, - error: transaction.transaction.error + error: transaction.transaction.error, + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } })) assert.deepEqual(expectedTransactions, body.resultSet) @@ -249,7 +275,11 @@ describe('Transaction routes', () => { type: transaction.transaction.type, gasUsed: transaction.transaction.gas_used, status: transaction.transaction.status, - error: transaction.transaction.error + error: transaction.transaction.error, + owner: { + identifier: transaction.transaction.owner, + aliases: [identityAlias.alias] + } })) assert.deepEqual(expectedTransactions, body.resultSet) diff --git a/packages/frontend/src/app/api/content.md b/packages/frontend/src/app/api/content.md index a770c6996..c1bd824a2 100644 --- a/packages/frontend/src/app/api/content.md +++ b/packages/frontend/src/app/api/content.md @@ -343,9 +343,14 @@ GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0 ``` --- ### Validator stats by ProTxHash -Return a series data for the amount of proposed blocks by validator chart with variable timespan (1h, 24h, 3d, 1w) +Return a series data for the amount of proposed blocks by validator chart with + +* `start` lower interval threshold in ISO string ( _optional_ ) +* `end` upper interval threshold in ISO string ( _optional_ ) + + ``` -GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0/stats?timespan=24h +GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0/stats?start=2024-01-01T00:00:00&end=2025-01-01T00:00:00 [ { timestamp: "2024-06-23T13:51:44.154Z", @@ -374,7 +379,11 @@ GET /transaction/DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEE type: 0, gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: { + identifier: "6q9RFbeea73tE31LGMBLFZhtBUX3wZL3TcNynqE18Zgs", + aliases: [] + } } ``` @@ -412,7 +421,11 @@ GET /transactions?=1&limit=10&order=asc type: 0, gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: { + identifier: "6q9RFbeea73tE31LGMBLFZhtBUX3wZL3TcNynqE18Zgs", + aliases: [] + } }, ... ] } @@ -730,7 +743,8 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transactions?page=1 timestamp: "2024-03-18T10:13:54.150Z", gasUsed: 1337000, status: "SUCCESS", - error: null + error: null, + owner: "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec" }, ... ] } @@ -772,9 +786,13 @@ Response codes: 500: Internal Server Error ``` ### Transactions history -Return a series data for the amount of transactions chart with variable timespan (1h, 24h, 3d, 1w) +Return a series data for the amount of transactions chart + +* `start` lower interval threshold in ISO string ( _optional_ ) +* `end` upper interval threshold in ISO string ( _optional_ ) + ``` -GET /transactions/history?timespan=1h +GET /transactions/history?start=2024-01-01T00:00:00&end=2025-01-01T00:00:00 [ { timestamp: "2024-04-22T08:45:20.911Z", @@ -797,7 +815,7 @@ GET /transactions/history?timespan=1h Response codes: ``` 200: OK -400: Invalid input, check timespan value +400: Invalid input, check start/end values 500: Internal Server Error ``` ### Rate