Skip to content

Commit

Permalink
Merge pull request #273 from pshenmic/feat/validators-info
Browse files Browse the repository at this point in the history
Validators info expansion
  • Loading branch information
pshenmic authored Oct 18, 2024
2 parents 2fa8866 + 1831467 commit 747e9b9
Show file tree
Hide file tree
Showing 22 changed files with 845 additions and 155 deletions.
1 change: 1 addition & 0 deletions packages/api/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ DASHCORE_USER=username
DASHCORE_PASS=password
EPOCH_CHANGE_TIME=3600000
DAPI_URL=127.0.0.1:1443:self-signed
TCP_CONNECT_TIMEOUT=400
88 changes: 68 additions & 20 deletions packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Reference:
* [Transactions By Identity](#transactions-by-identity)
* [Transfers by Identity](#transfers-by-identity)
* [Transactions history](#transactions-history)
* [Rate](#rates)
* [Rate](#rate)

### Status
Returns basic stats and epoch info
Expand Down Expand Up @@ -266,7 +266,21 @@ GET /validators
pubKeyOperator: "b928fa4e127214ccb2b5de1660b5e371d2f3c9845077bc3900fc6aabe82ddd2e61530be3765cea15752e30fc761ab730"
}
},
identity: "8tsWRSwsTM5AXv4ViCF9gu39kzjbtfFDM6rCyL2RcFzd"
identifier: "8tsWRSwsTM5AXv4ViCF9gu39kzjbtfFDM6rCyL2RcFzd",
identityBalance: 0,
epochInfo: {
number: 1982,
firstBlockHeight: 31976,
firstCoreBlockHeight: 1118131,
startTime: 1728488466559,
feeMultiplier: 1,
endTime: 1728492066559
},
totalReward: 0,
epochReward: 0,
withdrawalsCount: null,
lastWithdrawal: null,
lastWithdrawalTime: null,
}, ...
],
pagination: {
Expand Down Expand Up @@ -304,25 +318,56 @@ GET /validator/F60A6BF9EC0794BB0CFD1E0F2217933F4B33EDE6FE810692BC275CA18148AEF0
operatorReward: 0,
confirmations: 214424,
state: {
version: 2,
service: "35.164.23.245:19999",
registeredHeight: 850334,
lastPaidHeight: 1064721,
consecutivePayments: 0,
PoSePenalty: 0,
PoSeRevivedHeight: 1027671,
PoSeBanHeight: -1,
revocationReason: 0,
ownerAddress: "yWrbg8HNwkogZfqKe1VW8czS9KiqdjvJtE",
votingAddress: "yWrbg8HNwkogZfqKe1VW8czS9KiqdjvJtE",
platformNodeID: "b5f25f8f70cf8d05c2d2970bdf186c994431d84e",
platformP2PPort: 36656,
platformHTTPPort: 1443,
payoutAddress: "yeRZBWYfeNE4yVUHV4ZLs83Ppn9aMRH57A",
pubKeyOperator: "b928fa4e127214ccb2b5de1660b5e371d2f3c9845077bc3900fc6aabe82ddd2e61530be3765cea15752e30fc761ab730"
version: 2,
service: "35.164.23.245:19999",
registeredHeight: 850334,
lastPaidHeight: 1064721,
consecutivePayments: 0,
PoSePenalty: 0,
PoSeRevivedHeight: 1027671,
PoSeBanHeight: -1,
revocationReason: 0,
ownerAddress: "yWrbg8HNwkogZfqKe1VW8czS9KiqdjvJtE",
votingAddress: "yWrbg8HNwkogZfqKe1VW8czS9KiqdjvJtE",
platformNodeID: "b5f25f8f70cf8d05c2d2970bdf186c994431d84e",
platformP2PPort: 36656,
platformHTTPPort: 1443,
payoutAddress: "yeRZBWYfeNE4yVUHV4ZLs83Ppn9aMRH57A",
pubKeyOperator: "b928fa4e127214ccb2b5de1660b5e371d2f3c9845077bc3900fc6aabe82ddd2e61530be3765cea15752e30fc761ab730",
endpoints: {
coreP2PPortStatus: {
host: '52.33.28.41',
port: 19999,
status: 'ERROR'
},
platformP2PPortStatus: {
host: '52.33.28.41',
port: 36656,
status: 'ERROR'
},
platformGrpcPortStatus: {
host: '52.33.28.41',
port: 1443,
status: 'ERROR'
}
}
}
},
"identity: "8tsWRSwsTM5AXv4ViCF9gu39kzjbtfFDM6rCyL2RcFzd"
identifier: "8tsWRSwsTM5AXv4ViCF9gu39kzjbtfFDM6rCyL2RcFzd",
identityBalance: 0,
epochInfo: {
number: 1982,
firstBlockHeight: 31976,
firstCoreBlockHeight: 1118131,
startTime: 1728488466559,
feeMultiplier: 1,
endTime: 1728492066559
},
totalReward: 0,
epochReward: 0,
withdrawalsCount: 1,
lastWithdrawal: "01FE1F00379C66C6E3BFD81A088E57E17613EC36E4FF812458535A8ABCB84047",
lastWithdrawalTime: "2024-10-12T03:15:19.257Z"
}
```
---
Expand Down Expand Up @@ -729,7 +774,7 @@ Response codes:
Return all transfers made by the given identity
* `limit` cannot be more then 100
```
GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&limit=10&order=asc
GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&limit=10&order=asc&type=1
{
pagination: {
Expand All @@ -743,6 +788,9 @@ GET /identities/GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec/transfers?page=1&li
sender: "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec",
recipient: "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec",
timestamp: "2024-03-18T10:13:54.150Z",
txHash: "445E6F081DEE877867816AD3EF492E2C0BD1DDCCDC9C793B23DDDAF8AEA23118",
type: 6,
blockHash: "73171E0A8DCC10C6DA501E1C70A9C1E0BD6F1F8F834C2A1E787AF19B1F361D5E"
}, ...
]
}
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ let genesisTime

module.exports = {
EPOCH_CHANGE_TIME: Number(process.env.EPOCH_CHANGE_TIME),
TCP_CONNECT_TIMEOUT: Number(process.env.TCP_CONNECT_TIMEOUT),
get genesisTime () {
if (!genesisTime || isNaN(genesisTime)) {
return TenderdashRPC.getBlockByHeight(1).then((blockInfo) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/controllers/EpochController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class EpochController {
const { index } = request.params

try {
const [currentEpoch, nextEpoch] = await this.dapi.getEpochsInfo(2, index, true)
const [currentEpoch, nextEpoch] = await this.dapi.getEpochsInfo(2, index ?? undefined, index ? true : undefined)

const epoch = Epoch.fromObject({ ...currentEpoch, nextEpoch })

Expand Down
20 changes: 18 additions & 2 deletions packages/api/src/controllers/IdentitiesController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const IdentitiesDAO = require('../dao/IdentitiesDAO')
const { IDENTITY_CREDIT_WITHDRAWAL } = require('../enums/StateTransitionEnum')

class IdentitiesController {
constructor (knex, dapi) {
Expand Down Expand Up @@ -76,12 +77,27 @@ class IdentitiesController {

getTransfersByIdentity = async (request, response) => {
const { identifier } = request.params
const { page = 1, limit = 10, order = 'asc' } = request.query
const { page = 1, limit = 10, order = 'asc', type = undefined } = request.query

const transfers = await this.identitiesDAO.getTransfersByIdentity(identifier, Number(page ?? 1), Number(limit ?? 10), order)
const transfers = await this.identitiesDAO.getTransfersByIdentity(identifier, Number(page ?? 1), Number(limit ?? 10), order, type)

response.send(transfers)
}

getWithdrawalsByIdentity = async (request, response) => {
const { identifier } = request.params
const { page = 1, limit = 10, order = 'asc' } = request.query

const withdrawals = await this.identitiesDAO.getTransfersByIdentity(
identifier,
Number(page ?? 1),
Number(limit ?? 10),
order ?? 'asc',
IDENTITY_CREDIT_WITHDRAWAL
)

response.send(withdrawals)
}
}

module.exports = IdentitiesController
99 changes: 81 additions & 18 deletions packages/api/src/controllers/ValidatorsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,26 @@ const TenderdashRPC = require('../tenderdashRpc')
const Validator = require('../models/Validator')
const DashCoreRPC = require('../dashcoreRpc')
const ProTxInfo = require('../models/ProTxInfo')
const { checkTcpConnect } = require('../utils')
const Epoch = require('../models/Epoch')
const { base58 } = require('@scure/base')

class ValidatorsController {
constructor (knex) {
constructor (knex, dapi) {
this.validatorsDAO = new ValidatorsDAO(knex)
this.dapi = dapi
}

getValidatorByProTxHash = async (request, response) => {
const { hash } = request.params

const validator = await this.validatorsDAO.getValidatorByProTxHash(hash)
const [currentEpoch] = await this.dapi.getEpochsInfo(1)
const epochInfo = Epoch.fromObject(currentEpoch)

const identifier = base58.encode(Buffer.from(hash, 'hex'))
const identityBalance = await this.dapi.getIdentityBalance(identifier)

const validator = await this.validatorsDAO.getValidatorByProTxHash(hash, identifier, epochInfo)

if (!validator) {
return response.status(404).send({ message: 'not found' })
Expand All @@ -24,13 +34,49 @@ class ValidatorsController {

const isActive = validators.some(validator => validator.pro_tx_hash === hash)

const [host] = proTxInfo?.state.service ? proTxInfo?.state.service.match(/^\d+\.\d+\.\d+\.\d+/) : [null]
const [servicePort] = proTxInfo?.state.service ? proTxInfo?.state.service.match(/\d+$/) : [null]

const [coreStatus, platformStatus, grpcStatus] = (await Promise.allSettled([
checkTcpConnect(servicePort, host),
checkTcpConnect(proTxInfo?.state.platformP2PPort, host),
checkTcpConnect(proTxInfo?.state.platformHTTPPort, host)
])).map((e) => e?.reason.code ? e?.reason.code : e.message)

const endpoints = {
coreP2PPortStatus: {
host,
port: Number(servicePort),
status: coreStatus
},
platformP2PPortStatus: {
host,
port: Number(proTxInfo?.state.platformP2PPort),
status: platformStatus
},
platformGrpcPortStatus: {
host,
port: Number(proTxInfo?.state.platformHTTPPort ?? 0),
status: grpcStatus
}
}

response.send(
new Validator(
validator.proTxHash,
isActive,
validator.proposedBlocksAmount,
validator.lastProposedBlockHeader,
ProTxInfo.fromObject(proTxInfo)
Validator.fromObject(
{
...validator,
isActive,
proTxInfo: ProTxInfo.fromObject({
...proTxInfo,
state: {
...proTxInfo.state,
endpoints
}
}),
identifier,
identityBalance,
epochInfo
}
)
)
}
Expand All @@ -40,29 +86,46 @@ class ValidatorsController {

const activeValidators = await TenderdashRPC.getValidators()

const [currentEpoch] = await this.dapi.getEpochsInfo(1)
const epochInfo = Epoch.fromObject(currentEpoch)

const validators = await this.validatorsDAO.getValidators(
Number(page ?? 1),
Number(limit ?? 10),
order,
isActive,
activeValidators
activeValidators,
epochInfo
)

const validatorsWithInfo = await Promise.all(
validators.resultSet.map(async (validator) =>
({ ...validator, proTxInfo: await DashCoreRPC.getProTxInfo(validator.proTxHash) })))

const resultSet = await Promise.all(
validatorsWithInfo.map(
async (validator) => {
const identifier = validator.proTxHash ? base58.encode(Buffer.from(validator.proTxHash, 'hex')) : null
const identityBalance = identifier ? await this.dapi.getIdentityBalance(identifier) : null

return Validator.fromObject(
{
...validator,
isActive: activeValidators.some(activeValidator =>
activeValidator.pro_tx_hash === validator.proTxHash),
proTxInfo: ProTxInfo.fromObject(validator.proTxInfo),
identifier,
identityBalance,
epochInfo
}
)
}
)
)

return response.send({
...validators,
resultSet: validatorsWithInfo.map(validator =>
new Validator(
validator.proTxHash, activeValidators.some(activeValidator =>
activeValidator.pro_tx_hash === validator.proTxHash),
validator.proposedBlocksAmount,
validator.lastProposedBlockHeader,
ProTxInfo.fromObject(validator.proTxInfo)
)
)
resultSet
})
}

Expand Down
31 changes: 23 additions & 8 deletions packages/api/src/dao/IdentitiesDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,21 +266,36 @@ module.exports = class IdentitiesDAO {
return new PaginatedResultSet(rows.map(row => Transaction.fromRow(row)), page, limit, totalCount)
}

getTransfersByIdentity = async (identifier, page, limit, order) => {
getTransfersByIdentity = async (identifier, page, limit, order, type) => {
const fromRank = (page - 1) * limit + 1
const toRank = fromRank + limit - 1

const subquery = this.knex('transfers')
.select('transfers.id as id', 'transfers.amount as amount', 'transfers.sender as sender', 'transfers.recipient as recipient', 'transfers.state_transition_hash as tx_hash')
.select(this.knex.raw(`rank() over (order by id ${order}) rank`))
.where('transfers.sender', '=', identifier)
.orWhere('transfers.recipient', '=', identifier)
.select(
'transfers.id as id', 'transfers.amount as amount',
'transfers.sender as sender', 'transfers.recipient as recipient',
'transfers.state_transition_hash as tx_hash',
'state_transitions.block_hash as block_hash',
'state_transitions.type as type'

)
.select(this.knex.raw(`rank() over (order by transfers.id ${order}) rank`))
.whereRaw(`(transfers.sender = '${identifier}' OR transfers.recipient = '${identifier}') ${
typeof type === 'number'
? `AND state_transitions.type = ${type}`
: ''
}`)
.leftJoin('state_transitions', 'state_transitions.hash', 'transfers.state_transition_hash')

const rows = await this.knex.with('with_alias', subquery)
.select('with_alias.id', 'amount', 'sender', 'recipient', 'rank', 'tx_hash', 'blocks.timestamp as timestamp')
.select(
'rank', 'amount', 'block_hash', 'type',
'sender', 'recipient', 'with_alias.id',
'tx_hash', 'blocks.timestamp as timestamp',
'block_hash'
)
.select(this.knex('with_alias').count('*').as('total_count'))
.leftJoin('state_transitions', 'state_transitions.hash', 'tx_hash')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.leftJoin('blocks', 'blocks.hash', 'with_alias.block_hash')
.from('with_alias')
.whereBetween('rank', [fromRank, toRank])
.orderBy('with_alias.id', order)
Expand Down
Loading

0 comments on commit 747e9b9

Please sign in to comment.