-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Masternode RPC (createmasternode, listmasternodes, getmasternode) (#372)
Co-authored-by: canonbrother <[email protected]> Co-authored-by: Fuxing Loh <[email protected]>
- Loading branch information
1 parent
f02ad70
commit 58fba5e
Showing
7 changed files
with
489 additions
and
1 deletion.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
packages/jellyfish-api-core/__tests__/category/masternode/createMasternode.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
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') | ||
}) | ||
}) |
53 changes: 53 additions & 0 deletions
53
packages/jellyfish-api-core/__tests__/category/masternode/getMasternode.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
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') | ||
}) | ||
}) |
82 changes: 82 additions & 0 deletions
82
packages/jellyfish-api-core/__tests__/category/masternode/listMasternodes.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
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, | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.