Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Masternode RPC (createmasternode, listmasternodes, getmasternode) #372

Merged
merged 34 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ae3f84f
Masternode RPC (createmasternode, listmasternodes, getmasternode)
siradji Jun 11, 2021
459bfee
updated sidebar.js
siradji Jun 11, 2021
df6fd4b
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 11, 2021
13abb5d
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 11, 2021
cbc9760
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 11, 2021
bc5c327
Minor fixes
siradji Jun 11, 2021
6f908d1
Minor fixes
siradji Jun 11, 2021
e751f6c
Implementation of suggestion from code review.
siradji Jun 11, 2021
8306bc9
Additional testings for createMasternode
siradji Jun 11, 2021
481fd48
Suggesstions from code review
siradji Jun 14, 2021
700fcfc
Clean up
siradji Jun 15, 2021
f3256d4
fixing CI
siradji Jun 15, 2021
4217386
fixing issue with CI
siradji Jun 15, 2021
9845c20
Fixed issue with CI
siradji Jun 15, 2021
eea316c
Fixed issue with CI
siradji Jun 16, 2021
a854ada
Minor fixes
siradji Jun 16, 2021
056d7a0
Minor fixes
siradji Jun 17, 2021
374ac99
Minor fixes
siradji Jun 17, 2021
fceae28
Minor fixes
siradji Jun 18, 2021
ce443ea
Minor fixes
siradji Jun 19, 2021
a42c763
Minor fixes
siradji Jun 20, 2021
f05451e
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 21, 2021
782c2d3
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 21, 2021
37844d9
Minor fix
siradji Jun 21, 2021
29c0852
Update website/docs/jellyfish/api/masternode.md
fuxingloh Jun 22, 2021
53a0a23
Minor fixes
siradji Jun 22, 2021
b188f91
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
a89f236
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
f08a735
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
814c567
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
1216088
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
6469a39
Update packages/jellyfish-api-core/src/category/masternode.ts
siradji Jun 23, 2021
eceaa11
Minor fixes
siradji Jun 23, 2021
03a93f8
Update website/docs/jellyfish/api/masternode.md
fuxingloh Jun 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { MasternodeState } from '../../../src/category/masternode'

describe('Masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should createMasternode ', async () => {
const masternodesLengthBefore = Object.keys(await client.masternode.listMasternodes()).length

const ownerAddress = await client.wallet.getNewAddress()
const hex = await client.masternode.createMasternode(ownerAddress)

expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

await container.generate(1)

const masternodesAfter = await client.masternode.listMasternodes()
siradji marked this conversation as resolved.
Show resolved Hide resolved
const masternodesLengthAfter = Object.keys(masternodesAfter).length
const createdMasternode = Object.values(masternodesAfter).filter(mn => mn.ownerAuthAddress === ownerAddress)

expect(masternodesLengthAfter).toStrictEqual(masternodesLengthBefore + 1)

for (const mn of createdMasternode) {
expect(typeof mn.ownerAuthAddress).toStrictEqual('string')
expect(typeof mn.operatorAuthAddress).toStrictEqual('string')
expect(typeof mn.creationHeight).toStrictEqual('number')
expect(typeof mn.resignHeight).toStrictEqual('number')
expect(typeof mn.resignTx).toStrictEqual('string')
expect(typeof mn.banHeight).toStrictEqual('number')
expect(typeof mn.banTx).toStrictEqual('string')
expect(mn.state).toStrictEqual(MasternodeState.PRE_ENABLED)
expect(typeof mn.state).toStrictEqual('string')
expect(typeof mn.mintedBlocks).toStrictEqual('number')
expect(typeof mn.ownerIsMine).toStrictEqual('boolean')
expect(mn.ownerIsMine).toStrictEqual(true)
expect(typeof mn.localMasternode).toStrictEqual('boolean')
expect(typeof mn.operatorIsMine).toStrictEqual('boolean')
expect(mn.operatorIsMine).toStrictEqual(true)
}
})

it('should createMasternode with specified UTXOS', async () => {
const ownerAddress = await client.wallet.getNewAddress()
const { txid } = await container.fundAddress(ownerAddress, 10)

const utxos = await client.wallet.listUnspent()
const utxosBeforeLength = utxos.length

const inputs = utxos.filter((utxos) => utxos.txid === txid)
.map((utxo: { txid: string, vout: number }) => ({ txid: utxo.txid, vout: utxo.vout }))
const hex = await client.masternode.createMasternode(ownerAddress, ownerAddress, { utxos: inputs })

expect(typeof hex).toStrictEqual('string')
expect(hex.length).toStrictEqual(64)

const utxosAfterLength = (await client.wallet.listUnspent()).length
expect(utxosAfterLength).toBeLessThan((utxosBeforeLength))
})

it('should throw an error with invalid owner address', async () => {
const invalidAddress = 'invalidAddress'
await expect(client.masternode.createMasternode(invalidAddress)).rejects.toThrow('operatorAddress (invalidAddress) does not refer to a P2PKH or P2WPKH address')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { MasternodeState } from '../../../src/category/masternode'

describe('Masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should getMasternode', async () => {
const ownerAddress = await client.wallet.getNewAddress()
const id = await client.masternode.createMasternode(ownerAddress)

await container.generate(1)

const masternode = await client.masternode.getMasternode(id)

expect(Object.keys(masternode).length).toStrictEqual(1)
for (const masternodeKey in masternode) {
siradji marked this conversation as resolved.
Show resolved Hide resolved
const data = masternode[masternodeKey]
expect(typeof data.operatorAuthAddress).toStrictEqual('string')
expect(typeof data.ownerAuthAddress).toStrictEqual('string')
expect(typeof data.creationHeight).toStrictEqual('number')
expect(typeof data.resignHeight).toStrictEqual('number')
expect(typeof data.resignTx).toStrictEqual('string')
expect(typeof data.banHeight).toStrictEqual('number')
expect(typeof data.banTx).toStrictEqual('string')
expect(data.state).toStrictEqual(MasternodeState.PRE_ENABLED)
expect(typeof data.state).toStrictEqual('string')
expect(typeof data.mintedBlocks).toStrictEqual('number')
expect(typeof data.ownerIsMine).toStrictEqual('boolean')
expect(typeof data.localMasternode).toStrictEqual('boolean')
expect(typeof data.operatorIsMine).toStrictEqual('boolean')
expect(data.operatorIsMine).toStrictEqual(true)
}
})

it('should fail and throw an error with invalid masternode id', async () => {
const invalidId = '8d4d987dee688e400a0cdc899386f243250d3656d802231755ab4d28178c9816'
const promise = client.masternode.getMasternode(invalidId)

await expect(promise).rejects.toThrow('Masternode not found')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import { MasternodePagination, MasternodeState } from '../../../src/category/masternode'

describe('Masternode', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

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

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

it('should listMasternodes', async () => {
const masternodeList = await client.masternode.listMasternodes()

expect(Object.keys(masternodeList).length).toBeGreaterThanOrEqual(1)
for (const masternode in masternodeList) {
const currentMasternode = masternodeList[masternode]
expect(typeof currentMasternode.ownerAuthAddress).toStrictEqual('string')
expect(typeof currentMasternode.operatorAuthAddress).toStrictEqual('string')
expect(typeof currentMasternode.creationHeight).toStrictEqual('number')
expect(typeof currentMasternode.resignHeight).toStrictEqual('number')
expect(typeof currentMasternode.resignTx).toStrictEqual('string')
expect(typeof currentMasternode.banHeight).toStrictEqual('number')
expect(typeof currentMasternode.banTx).toStrictEqual('string')
expect(currentMasternode.state).toStrictEqual(MasternodeState.ENABLED)
expect(typeof currentMasternode.mintedBlocks).toStrictEqual('number')
expect(typeof currentMasternode.ownerIsMine).toStrictEqual('boolean')
expect(typeof currentMasternode.localMasternode).toStrictEqual('boolean')
expect(typeof currentMasternode.operatorIsMine).toStrictEqual('boolean')
}
})

it('should listMasternodes with verbose false', async () => {
const masternodeList = await client.masternode.listMasternodes({}, false)
for (const value of Object.values(masternodeList)) {
expect(typeof value).toStrictEqual('string')
}
})

it('should listMasternodes with limit', async () => {
const masternodeList = await client.masternode.listMasternodes({ limit: 3 })
expect(Object.keys(masternodeList).length).toStrictEqual(3)
})

it('should listMasternodes with pagination start and including_start', async () => {
const prevMasternodes = await client.masternode.listMasternodes()
const startId = Object.keys(prevMasternodes)[2]
const masterNodeLengthBefore = Object.keys(prevMasternodes).length

const pagination: MasternodePagination = {
start: startId,
including_start: true
}
const masternodeList = await client.masternode.listMasternodes(pagination)

const masternodeLengthAfter = Object.keys(masternodeList).length
expect(masternodeLengthAfter).toStrictEqual(masterNodeLengthBefore - 2)

for (const masternode in masternodeList) {
const currentMasternode = masternodeList[masternode]
expect(typeof currentMasternode.ownerAuthAddress).toStrictEqual('string')
expect(typeof currentMasternode.operatorAuthAddress).toStrictEqual('string')
expect(typeof currentMasternode.creationHeight).toStrictEqual('number')
expect(typeof currentMasternode.resignHeight).toStrictEqual('number')
expect(typeof currentMasternode.resignTx).toStrictEqual('string')
expect(typeof currentMasternode.banHeight).toStrictEqual('number')
expect(typeof currentMasternode.banTx).toStrictEqual('string')
expect(currentMasternode.state).toStrictEqual(MasternodeState.ENABLED)
expect(typeof currentMasternode.mintedBlocks).toStrictEqual('number')
expect(typeof currentMasternode.ownerIsMine).toStrictEqual('boolean')
expect(typeof currentMasternode.localMasternode).toStrictEqual('boolean')
expect(typeof currentMasternode.operatorIsMine).toStrictEqual('boolean')
}
})
siradji marked this conversation as resolved.
Show resolved Hide resolved
siradji marked this conversation as resolved.
Show resolved Hide resolved
})
142 changes: 142 additions & 0 deletions packages/jellyfish-api-core/src/category/masternode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { ApiClient } from '../.'

export enum MasternodeState {
PRE_ENABLED = 'PRE_ENABLED',
ENABLED = 'ENABLED',
PRE_RESIGNED = 'PRE_RESIGNED',
RESIGNED = 'RESIGNED',
PRE_BANNED = 'PRE_BANNED',
BANNED = 'BANNED',
UNKNOWN = 'UNKNOWN'
}

/**
* Masternode RPCs for DeFi Blockchain
*/
export class Masternode {
siradji marked this conversation as resolved.
Show resolved Hide resolved
private readonly client: ApiClient

constructor (client: ApiClient) {
this.client = client
}

/**
* Creates a masternode creation transaction with given owner and operator addresses.
*
* @param {string} ownerAddress Any valid address for keeping collateral amount
* @param {string} [operatorAddress] Masternode operator auth address (P2PKH only, unique). If empty, owner address will be used.
* @param {CreateMasternodeOptions} [options]
* @param {UTXO[]} [options.utxos = []]
* @param {string} [options.uxtos.txid] The transaction id
* @param {string} [options.utxos.vout] The output number
* @return {Promise<string>}
*/
async createMasternode (
ownerAddress: string,
operatorAddress?: string,
options: CreateMasternodeOptions = { utxos: [] }
): Promise<string> {
operatorAddress = operatorAddress ?? ownerAddress
return await this.client.call('createmasternode', [ownerAddress, operatorAddress, options.utxos], 'number')
}

/**
* Returns information about multiple masternodes.
*
* @param {MasternodePagination} pagination
* @param {string} [pagination.start]
* @param {boolean} [pagination.including_start = true] Include starting position.
* @param {string} [pagination.limit = 100] Maximum number of orders to return.
* @param {boolean} [verbose = true] Flag for verbose list. Only ids are returned when false.
* @return {Promise<MasternodeResult<MasternodeInfo>>}
*/
listMasternodes (pagination?: MasternodePagination, verbose?: boolean): Promise<MasternodeResult<MasternodeInfo>>

/**
* Returns information about multiple masternodes.
*
* @param {MasternodePagination} pagination
* @param {string} [pagination.start]
* @param {boolean} [pagination.including_start = true] Include starting position.
* @param {string} [pagination.limit = 100] Maximum number of orders to return.
* @param {boolean} verbose true
* @return {Promise<MasternodeResult<MasternodeInfo>>}
*/
listMasternodes (pagination: MasternodePagination, verbose: true): Promise<MasternodeResult<MasternodeInfo>>

/**
* Returns information about multiple masternodes.
*
* @param {MasternodePagination} pagination
* @param {string} [pagination.start]
* @param {boolean} [pagination.including_start = true] Include starting position.
* @param {string} [pagination.limit = 100] Maximum number of orders to return.
* @param {boolean} verbose false.
* @return {Promise<MasternodeResult<string>>}
*/
listMasternodes (pagination: MasternodePagination, verbose: false): Promise<MasternodeResult<string>>

/**
* Returns information about multiple masternodes.
*
* @param {MasternodePagination} pagination
* @param {string} [pagination.start]
* @param {boolean} [pagination.including_start = true] Include starting position.
* @param {string} [pagination.limit = 100] Maximum number of orders to return.
* @param {boolean} [verbose = true] Flag for verbose list. Only ids are returned when false.
* @return {Promise<MasternodeResult<T>>}
*/
async listMasternodes<T> (
pagination: MasternodePagination = {
including_start: true,
siradji marked this conversation as resolved.
Show resolved Hide resolved
limit: 100
},
verbose: boolean = true
): Promise<MasternodeResult<T>> {
return await this.client.call('listmasternodes', [pagination, verbose], 'number')
}

/**
* Returns information about a single masternode
*
* @param {string} masternodeId The masternode's id.
* @return {Promise<MasternodeResult>}
*/
async getMasternode (masternodeId: string): Promise<MasternodeResult<MasternodeInfo>> {
return await this.client.call('getmasternode', [masternodeId], 'number')
}
}

export interface UTXO {
txid: string
vout: number
}

export interface CreateMasternodeOptions {
utxos: UTXO[]
}

export interface MasternodePagination {
start?: string
including_start?: boolean
limit?: number
}

export interface MasternodeInfo {
ownerAuthAddress: string
operatorAuthAddress: string
creationHeight: number
resignHeight: number
resignTx: string
banHeight: number
banTx: string
state: MasternodeState
mintedBlocks: number
ownerIsMine: boolean
operatorIsMine: boolean
localMasternode: boolean
}

export interface MasternodeResult<T> {
[id: string]: T
}
3 changes: 3 additions & 0 deletions packages/jellyfish-api-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PoolPair } from './category/poolpair'
import { Token } from './category/token'
import { Oracle } from './category/oracle'
import { Server } from './category/server'
import { Masternode } from './category/masternode'

export * from '@defichain/jellyfish-json'

Expand All @@ -22,6 +23,7 @@ export * as token from './category/token'
export * as account from './category/account'
export * as oracle from './category/oracle'
export * as server from './category/server'
export * as masternode from './category/masternode'

/**
* A protocol agnostic DeFiChain node client, RPC calls are separated into their category.
Expand All @@ -37,6 +39,7 @@ export abstract class ApiClient {
public readonly token = new Token(this)
public readonly oracle = new Oracle(this)
public readonly server = new Server(this)
public readonly masternode = new Masternode(this)

/**
* A promise based procedure call handling
Expand Down
Loading