Skip to content
This repository has been archived by the owner on Jun 3, 2022. It is now read-only.

Commit

Permalink
Masternode controller (#204)
Browse files Browse the repository at this point in the history
* WIP [masternode controller]

* Masternode.spec.ts

* Masternode.controller.e2e.ts

* Cleanup and CI fixes

* Code governace fix and minor fixes

* clean up and improvements

* Minor fixes

* Minor fixes

* Fix ci failure

* Implemented quested advise on PR

* Minor fixes

* Minor fixes

* updated masternode tests to use genesis masternodes

* Improved code better testing naming

* Apply suggestions from code review

* Added more tests assertations

Co-authored-by: Fuxing Loh <[email protected]>
  • Loading branch information
siradji and fuxingloh authored Jul 16, 2021
1 parent 882a56d commit 7dbf888
Show file tree
Hide file tree
Showing 8 changed files with 427 additions and 1 deletion.
120 changes: 120 additions & 0 deletions packages/whale-api-client/__tests__/api/masternode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { StubWhaleApiClient } from '../stub.client'
import { StubService } from '../stub.service'
import { WhaleApiClient, WhaleApiException } from '../../src'

let container: MasterNodeRegTestContainer
let service: StubService
let client: WhaleApiClient

beforeAll(async () => {
container = new MasterNodeRegTestContainer()
service = new StubService(container)
client = new StubWhaleApiClient(service)

await container.start()
await container.waitForReady()
await service.start()
})

afterAll(async () => {
try {
await service.stop()
} finally {
await container.stop()
}
})

describe('list', () => {
it('should list masternodes', async () => {
const data = await client.masternodes.list()
expect(Object.keys(data[0]).length).toStrictEqual(7)
expect(data.hasNext).toStrictEqual(false)
expect(data.nextToken).toStrictEqual(undefined)

expect(data[0]).toStrictEqual({
id: '03280abd3d3ae8dc294c1a572cd7912c3c3e53044943eac62c2f6c4687c87f10',
state: 'ENABLED',
mintedBlocks: 0,
owner: { address: 'bcrt1qyeuu9rvq8a67j86pzvh5897afdmdjpyankp4mu' },
operator: { address: 'bcrt1qurwyhta75n2g75u2u5nds9p6w9v62y8wr40d2r' },
creation: { height: 0 },
resign: {
tx: '0000000000000000000000000000000000000000000000000000000000000000',
height: -1
}
})
})

it('should list masternodes with pagination', async () => {
const first = await client.masternodes.list(4)
expect(first.length).toStrictEqual(4)
expect(first.hasNext).toStrictEqual(true)
expect(first.nextToken).toStrictEqual(first[3].id)

const next = await client.paginate(first)
expect(next.length).toStrictEqual(4)
expect(next.hasNext).toStrictEqual(true)
expect(next.nextToken).toStrictEqual(next[3].id)

const last = await client.paginate(next)
expect(last.length).toStrictEqual(0)
expect(last.hasNext).toStrictEqual(false)
expect(last.nextToken).toStrictEqual(undefined)
})
})

describe('get', () => {
it('should get masternode', async () => {
// get a masternode from list
const masternode = (await client.masternodes.list(1))[0]

const data = await client.masternodes.get(masternode.id)
expect(Object.keys(data).length).toStrictEqual(7)
expect(data).toStrictEqual({
id: masternode.id,
state: masternode.state,
mintedBlocks: masternode.mintedBlocks,
owner: { address: masternode.owner.address },
operator: { address: masternode.operator.address },
creation: { height: masternode.creation.height },
resign: {
tx: masternode.resign.tx,
height: masternode.resign.height
}
})
})

it('should fail due to non-existent masternode', async () => {
expect.assertions(2)
const id = '8d4d987dee688e400a0cdc899386f243250d3656d802231755ab4d28178c9816'
try {
await client.masternodes.get(id)
} catch (err) {
expect(err).toBeInstanceOf(WhaleApiException)
expect(err.error).toStrictEqual({
code: 404,
type: 'NotFound',
at: expect.any(Number),
message: 'Unable to find masternode',
url: `/v0/regtest/masternodes/${id}`
})
}
})

it('should fail and throw an error with malformed id', async () => {
expect.assertions(2)
try {
await client.masternodes.get('sdh183')
} catch (err) {
expect(err).toBeInstanceOf(WhaleApiException)
expect(err.error).toStrictEqual({
code: 400,
type: 'BadRequest',
at: expect.any(Number),
message: "RpcApiError: 'masternode id must be of length 64 (not 6, for 'sdh183')', code: -8, method: getmasternode",
url: '/v0/regtest/masternodes/sdh183'
})
}
})
})
66 changes: 66 additions & 0 deletions packages/whale-api-client/src/api/masternode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { WhaleApiClient } from '../whale.api.client'
import { ApiPagedResponse } from '../whale.api.response'

/**
* DeFi whale endpoint for masternode related services.
*/
export class Masternodes {
constructor (private readonly client: WhaleApiClient) {
}

/**
* Get list of masternodes.
*
* @param {number} size masternodes size to query
* @param {string} next set of masternodes to get
* @return {Promise<ApiPagedResponse<MasternodeData>>}
*/
async list (size: number = 30, next?: string): Promise<ApiPagedResponse<MasternodeData>> {
return await this.client.requestList('GET', 'masternodes', size, next)
}

/**
* Get information about a masternode with given id.
*
* @param {string} id masternode id to get
* @return {Promise<MasternodeData>}
*/
async get (id: string): Promise<MasternodeData> {
return await this.client.requestData('GET', `masternodes/${id}`)
}
}

/**
* Masternode data
*/
export interface MasternodeData {
id: string
state: MasternodeState
mintedBlocks: number
owner: {
address: string
}
operator: {
address: string
}
creation: {
height: number
}
resign: {
tx: string
height: number
}
}

/**
* Masternode state
*/
export enum MasternodeState {
PRE_ENABLED = 'PRE_ENABLED',
ENABLED = 'ENABLED',
PRE_RESIGNED = 'PRE_RESIGNED',
RESIGNED = 'RESIGNED',
PRE_BANNED = 'PRE_BANNED',
BANNED = 'BANNED',
UNKNOWN = 'UNKNOWN'
}
1 change: 1 addition & 0 deletions packages/whale-api-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * as poolpair from './api/poolpair'
export * as rpc from './api/rpc'
export * as transactions from './api/transactions'
export * as tokens from './api/tokens'
export * as masternodes from './api/masternode'

export * from './whale.api.client'
export * from './whale.api.response'
Expand Down
2 changes: 2 additions & 0 deletions packages/whale-api-client/src/whale.api.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PoolPair } from './api/poolpair'
import { Rpc } from './api/rpc'
import { Transactions } from './api/transactions'
import { Tokens } from './api/tokens'
import { Masternodes } from './api/masternode'

/**
* WhaleApiClient Options
Expand Down Expand Up @@ -58,6 +59,7 @@ export class WhaleApiClient {
public readonly rpc = new Rpc(this)
public readonly transactions = new Transactions(this)
public readonly tokens = new Tokens(this)
public readonly masternodes = new Masternodes(this)

constructor (
private readonly options: WhaleApiClientOptions
Expand Down
4 changes: 3 additions & 1 deletion src/module.api/_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NetworkGuard } from '@src/module.api/guards/network.guard'
import { ExceptionInterceptor } from '@src/module.api/interceptors/exception.interceptor'
import { ResponseInterceptor } from '@src/module.api/interceptors/response.interceptor'
import { TokensController } from '@src/module.api/token.controller'
import { MasternodesController } from '@src/module.api/masternode.controller'

/**
* Exposed ApiModule for public interfacing
Expand All @@ -23,7 +24,8 @@ import { TokensController } from '@src/module.api/token.controller'
ActuatorController,
TransactionsController,
TokensController,
PoolPairController
PoolPairController,
MasternodesController
],
providers: [
{ provide: APP_PIPE, useClass: ApiValidationPipe },
Expand Down
73 changes: 73 additions & 0 deletions src/module.api/masternode.controller.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { createTestingApp, stopTestingApp } from '@src/e2e.module'
import { NotFoundException } from '@nestjs/common'
import { MasternodesController } from '@src/module.api/masternode.controller'

const container = new MasterNodeRegTestContainer()
let app: NestFastifyApplication
let controller: MasternodesController

beforeAll(async () => {
await container.start()
await container.waitForReady()

app = await createTestingApp(container)
controller = app.get(MasternodesController)
})

afterAll(async () => {
await stopTestingApp(container, app)
})

describe('list', () => {
it('should list masternodes', async () => {
const result = await controller.list({ size: 4 })
expect(result.data.length).toStrictEqual(4)
expect(Object.keys(result.data[0]).length).toStrictEqual(7)
})

it('should list masternodes with pagination', async () => {
const first = await controller.list({ size: 4 })
expect(first.data.length).toStrictEqual(4)

const next = await controller.list({
size: 4,
next: first.page?.next
})
expect(next.data.length).toStrictEqual(4)
expect(next.page?.next).toStrictEqual(next.data[3].id)

const last = await controller.list({
size: 4,
next: next.page?.next
})
expect(last.data.length).toStrictEqual(0)
expect(last.page).toStrictEqual(undefined)
})
})

describe('get', () => {
it('should get a masternode with id', async () => {
// get a masternode from list
const masternode = (await controller.list({ size: 1 })).data[0]

const result = await controller.get(masternode.id)
expect(Object.keys(result).length).toStrictEqual(7)
expect(result).toStrictEqual(masternode)
})

it('should fail due to non-existent masternode', async () => {
expect.assertions(2)
try {
await controller.get('8d4d987dee688e400a0cdc899386f243250d3656d802231755ab4d28178c9816')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find masternode',
error: 'Not Found'
})
}
})
})
84 changes: 84 additions & 0 deletions src/module.api/masternode.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Test, TestingModule } from '@nestjs/testing'
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import { MasternodesController } from '@src/module.api/masternode.controller'
import { CacheModule, NotFoundException } from '@nestjs/common'
import { DeFiDCache } from './cache/defid.cache'

const container = new MasterNodeRegTestContainer()
let controller: MasternodesController

beforeAll(async () => {
await container.start()
await container.waitForReady()
const client = new JsonRpcClient(await container.getCachedRpcUrl())

const app: TestingModule = await Test.createTestingModule({
imports: [
CacheModule.register()
],
controllers: [MasternodesController],
providers: [
{ provide: JsonRpcClient, useValue: client },
DeFiDCache
]
}).compile()

controller = app.get(MasternodesController)
})

afterAll(async () => {
await container.stop()
})

describe('list', () => {
it('should list masternodes', async () => {
const result = await controller.list({ size: 4 })
expect(result.data.length).toStrictEqual(4)
expect(Object.keys(result.data[0]).length).toStrictEqual(7)
})

it('should list masternodes with pagination', async () => {
const first = await controller.list({ size: 4 })
expect(first.data.length).toStrictEqual(4)

const next = await controller.list({
size: 4,
next: first.page?.next
})
expect(next.data.length).toStrictEqual(4)
expect(next.page?.next).toStrictEqual(next.data[3].id)

const last = await controller.list({
size: 4,
next: next.page?.next
})
expect(last.data.length).toStrictEqual(0)
expect(last.page).toStrictEqual(undefined)
})
})

describe('get', () => {
it('should get a masternode with id', async () => {
// get a masternode from list
const masternode = (await controller.list({ size: 1 })).data[0]

const result = await controller.get(masternode.id)
expect(Object.keys(result).length).toStrictEqual(7)
expect(result).toStrictEqual(masternode)
})

it('should fail due to non-existent masternode', async () => {
expect.assertions(2)
try {
await controller.get('8d4d987dee688e400a0cdc899386f243250d3656d802231755ab4d28178c9816')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find masternode',
error: 'Not Found'
})
}
})
})
Loading

0 comments on commit 7dbf888

Please sign in to comment.