Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
owl352 committed Sep 20, 2024
1 parent dedcdb6 commit 58b4a9c
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ Return all validators with pagination info.
* `lastProposedBlockHeader` field is nullable
* `?isActive=true` boolean can be supplied in the query params to filter by isActive field
* `limit` cannot be more then 100
* `all` boolean can be supplied in the query params to get all validators by params
```
GET /validators
Expand Down
5 changes: 3 additions & 2 deletions packages/api/src/controllers/ValidatorsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ValidatorsController {
}

getValidators = async (request, response) => {
const { page = 1, limit = 10, order = 'asc', isActive = undefined } = request.query
const { page = 1, limit = 10, order = 'asc', isActive = undefined, all = false } = request.query

const activeValidators = await TenderdashRPC.getValidators()

Expand All @@ -45,7 +45,8 @@ class ValidatorsController {
Number(limit),
order,
isActive,
activeValidators
activeValidators,
all
)

const validatorsWithInfo = await Promise.all(
Expand Down
18 changes: 9 additions & 9 deletions packages/api/src/dao/ValidatorsDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const PaginatedResultSet = require('../models/PaginatedResultSet')
const SeriesData = require('../models/SeriesData')

module.exports = class ValidatorsDAO {
constructor (knex) {
constructor(knex) {
this.knex = knex
}

Expand Down Expand Up @@ -62,9 +62,9 @@ module.exports = class ValidatorsDAO {
return Validator.fromRow(row)
}

getValidators = async (page, limit, order, isActive, validators) => {
getValidators = async (page, limit, order, isActive, validators, all) => {
const fromRank = ((page - 1) * limit) + 1
const toRank = fromRank + limit - 1
const toRank = all ? this.knex.raw("'+infinity'::numeric") : fromRank + limit - 1

const validatorsSubquery = this.knex('validators')
.select(
Expand Down Expand Up @@ -137,15 +137,15 @@ module.exports = class ValidatorsDAO {

const resultSet = rows.map((row) => Validator.fromRow(row))

return new PaginatedResultSet(resultSet, page, limit, totalCount)
return new PaginatedResultSet(resultSet, page, all ? resultSet.length : limit, totalCount)
}

getValidatorStatsByProTxHash = async (proTxHash, timespan) => {
const interval = {
'1h': { offset: '1 hour', step: '5 minute' },
'24h': { offset: '24 hour', step: '2 hour' },
'3d': { offset: '3 day', step: '6 hour' },
'1w': { offset: '1 week', step: '14 hour' }
'1h': {offset: '1 hour', step: '5 minute'},
'24h': {offset: '24 hour', step: '2 hour'},
'3d': {offset: '3 day', step: '6 hour'},
'1w': {offset: '1 week', step: '14 hour'}
}[timespan]

const ranges = this.knex
Expand All @@ -171,6 +171,6 @@ module.exports = class ValidatorsDAO {
blocksCount: parseInt(row.blocks_count)
}
}))
.map(({ timestamp, data }) => new SeriesData(timestamp, data))
.map(({timestamp, data}) => new SeriesData(timestamp, data))
}
}
4 changes: 4 additions & 0 deletions packages/api/src/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const schemaTypes = [
minimum: 1,
maximum: 100
},
all: {
type: ['boolean', 'null'],
default: false
},
order: {
type: ['string', 'null'],
enum: ['asc', 'desc']
Expand Down
261 changes: 261 additions & 0 deletions packages/api/test/integration/validators.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,101 @@ describe('Validators routes', () => {
assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return set of all validators', async () => {
const { body } = await client.get('/validators?all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, validators.length)
assert.equal(body.pagination.total, validators.length)
assert.equal(body.resultSet.length, validators.length)

const expectedValidators = validators
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash),
proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length,
lastProposedBlockHeader: blocks
.filter((block) => block.validator === row.pro_tx_hash)
.map((block) => BlockHeader.fromRow(block))
.map((blockHeader) => ({
hash: blockHeader.hash,
height: blockHeader.height,
timestamp: blockHeader.timestamp.toISOString(),
blockVersion: blockHeader.blockVersion,
appVersion: blockHeader.appVersion,
l1LockedHeight: blockHeader.l1LockedHeight,
validator: blockHeader.validator
}))
.toReversed()[0] ?? null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return default set of validators order desc', async () => {
const { body } = await client.get('/validators?order=desc&all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, validators.length)
assert.equal(body.pagination.total, validators.length)
assert.equal(body.resultSet.length, validators.length)

const expectedValidators = validators
.toReversed()
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: activeValidators.some(validator => validator.pro_tx_hash === row.pro_tx_hash),
proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length,
lastProposedBlockHeader: blocks
.filter((block) => block.validator === row.pro_tx_hash)
.map((block) => BlockHeader.fromRow(block))
.map((blockHeader) => ({
hash: blockHeader.hash,
height: blockHeader.height,
timestamp: blockHeader.timestamp.toISOString(),
blockVersion: blockHeader.blockVersion,
appVersion: blockHeader.appVersion,
l1LockedHeight: blockHeader.l1LockedHeight,
validator: blockHeader.validator
}))
.toReversed()[0] ?? null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return default set of validators order desc', async () => {
const { body } = await client.get('/validators?order=desc')
.expect(200)
Expand Down Expand Up @@ -611,6 +706,101 @@ describe('Validators routes', () => {
assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return set of all validators', async () => {
const { body } = await client.get('/validators?isActive=true&all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, activeValidators.length)
assert.equal(body.pagination.total, activeValidators.length)
assert.equal(body.resultSet.length, activeValidators.length)

const expectedValidators = activeValidators
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: true,
proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length,
lastProposedBlockHeader: blocks
.filter((block) => block.validator === row.pro_tx_hash)
.map((block) => BlockHeader.fromRow(block))
.map((blockHeader) => ({
hash: blockHeader.hash,
height: blockHeader.height,
timestamp: blockHeader.timestamp.toISOString(),
blockVersion: blockHeader.blockVersion,
appVersion: blockHeader.appVersion,
l1LockedHeight: blockHeader.l1LockedHeight,
validator: blockHeader.validator
}))
.toReversed()[0] ?? null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return set of all validators order desc', async () => {
const { body } = await client.get('/validators?order=desc&isActive=true&all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, activeValidators.length)
assert.equal(body.pagination.total, activeValidators.length)
assert.equal(body.resultSet.length, activeValidators.length)

const expectedValidators = activeValidators
.toReversed()
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: true,
proposedBlocksAmount: blocks.filter((block) => block.validator === row.pro_tx_hash).length,
lastProposedBlockHeader: blocks
.filter((block) => block.validator === row.pro_tx_hash)
.map((block) => BlockHeader.fromRow(block))
.map((blockHeader) => ({
hash: blockHeader.hash,
height: blockHeader.height,
timestamp: blockHeader.timestamp.toISOString(),
blockVersion: blockHeader.blockVersion,
appVersion: blockHeader.appVersion,
l1LockedHeight: blockHeader.l1LockedHeight,
validator: blockHeader.validator
}))
.toReversed()[0] ?? null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return default set of validators order desc', async () => {
const { body } = await client.get('/validators?order=desc&isActive=true')
.expect(200)
Expand Down Expand Up @@ -954,6 +1144,77 @@ describe('Validators routes', () => {
assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return set of all validators', async () => {
const { body } = await client.get('/validators?isActive=false&all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, inactiveValidators.length)
assert.equal(body.pagination.total, inactiveValidators.length)
assert.equal(body.resultSet.length, inactiveValidators.length)

const expectedValidators = inactiveValidators
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: false,
proposedBlocksAmount: 0,
lastProposedBlockHeader: null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return set of all validators order desc', async () => {
const { body } = await client.get('/validators?order=desc&isActive=false&all=true')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')

assert.equal(body.pagination.page, 1)
assert.equal(body.pagination.limit, inactiveValidators.length)
assert.equal(body.pagination.total, inactiveValidators.length)
assert.equal(body.resultSet.length, inactiveValidators.length)

const expectedValidators = inactiveValidators
.toReversed()
.map(row => {
const identity = identities.find(identity =>
identity.identifier === Base58.encode(Buffer.from(row.pro_tx_hash, 'hex')))
return {
proTxHash: row.pro_tx_hash,
isActive: false,
proposedBlocksAmount: 0,
lastProposedBlockHeader: null,
proTxInfo: {
type: dashCoreRpcResponse.type,
collateralHash: dashCoreRpcResponse.collateralHash,
collateralIndex: dashCoreRpcResponse.collateralIndex,
collateralAddress: dashCoreRpcResponse.collateralAddress,
operatorReward: dashCoreRpcResponse.operatorReward,
confirmations: dashCoreRpcResponse.confirmations,
state: dashCoreRpcResponse.state
},
identity: identity.identifier
}
})

assert.deepEqual(body.resultSet, expectedValidators)
})

it('should return default set of validators order desc', async () => {
const { body } = await client.get('/validators?order=desc&isActive=false')
.expect(200)
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/app/api/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Return all validators with pagination info.
* `lastProposedBlockHeader` field is nullable
* `?isActive=true` boolean can be supplied in the query params to filter by isActive field
* `limit` cannot be more then 100
* `all` boolean can be supplied in the query params to get all validators by params
```
GET /validators
Expand Down

0 comments on commit 58b4a9c

Please sign in to comment.