-
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.
jellyfish-core protocol adapter and error handling (#41)
* removed main/test net workflow * testcontainers for defichain js ecosystem * check in .idea code styles * automatically pull docker image for testing * cleanup code debt * added escape-hatch, unable to find open handles * added DeFiDRpcError for easier downstream error surfacing * removed json-bigint from container * testcontainers for defichain js ecosystem * check in .idea code styles * removed json-bigint from container * added precision test code updated jest and package config * added container adapter client with mining test * added getNetworkHashPerSecond * added jellyfish-core error handling * moved container_adapter_client.ts to root of test
- Loading branch information
Showing
11 changed files
with
325 additions
and
28 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,81 @@ | ||
import { RegTestContainer, MasterNodeRegTestContainer } from '@defichain/testcontainers' | ||
import { ContainerAdapterClient } from '../container_adapter_client' | ||
import waitForExpect from 'wait-for-expect' | ||
|
||
describe('non masternode', () => { | ||
const container = new RegTestContainer() | ||
const client = new ContainerAdapterClient(container) | ||
|
||
beforeAll(async () => { | ||
await container.start() | ||
await container.waitForReady() | ||
}) | ||
|
||
afterAll(async () => { | ||
await container.stop() | ||
}) | ||
|
||
it('should getMintingInfo', async () => { | ||
const info = await client.mining.getMintingInfo() | ||
|
||
expect(info.blocks).toBe(0) | ||
expect(info.difficulty).toBeDefined() | ||
expect(info.isoperator).toBe(false) | ||
expect(info.networkhashps).toBe(0) | ||
expect(info.pooledtx).toBe(0) | ||
expect(info.chain).toBe('regtest') | ||
expect(info.warnings).toBe('') | ||
}) | ||
|
||
it('should getNetworkHashPerSecond', async () => { | ||
const result = await client.mining.getNetworkHashPerSecond() | ||
expect(result).toBe(0) | ||
}) | ||
}) | ||
|
||
describe('masternode', () => { | ||
const container = new MasterNodeRegTestContainer() | ||
const client = new ContainerAdapterClient(container) | ||
|
||
beforeAll(async () => { | ||
await container.start() | ||
await container.waitForReady() | ||
}) | ||
|
||
afterAll(async () => { | ||
await container.stop() | ||
}) | ||
|
||
it('should getMintingInfo', async () => { | ||
const info = await client.mining.getMintingInfo() | ||
|
||
await waitForExpect(async () => { | ||
const info = await container.getMintingInfo() | ||
expect(info.blocks).toBeGreaterThan(1) | ||
}) | ||
|
||
expect(info.blocks).toBeGreaterThan(0) | ||
|
||
expect(info.currentblockweight).toBeGreaterThan(0) | ||
expect(info.currentblocktx).toBe(0) | ||
|
||
expect(info.difficulty).toBeDefined() | ||
expect(info.isoperator).toBe(true) | ||
|
||
expect(info.masternodeid).toBeDefined() | ||
expect(info.masternodeoperator).toBeDefined() | ||
expect(info.masternodestate).toBe('ENABLED') | ||
expect(info.generate).toBe(true) | ||
expect(info.mintedblocks).toBe(0) | ||
|
||
expect(info.networkhashps).toBeGreaterThan(0) | ||
expect(info.pooledtx).toBe(0) | ||
expect(info.chain).toBe('regtest') | ||
expect(info.warnings).toBe('') | ||
}) | ||
|
||
it('should getNetworkHashPerSecond', async () => { | ||
const result = await client.mining.getNetworkHashPerSecond() | ||
expect(result).toBeGreaterThan(0) | ||
}) | ||
}) |
38 changes: 38 additions & 0 deletions
38
packages/jellyfish-core/__tests__/container_adapter_client.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,38 @@ | ||
import { JellyfishClient, JellyfishError } from '../src/core' | ||
import { DeFiDContainer } from '@defichain/testcontainers' | ||
|
||
/** | ||
* Jellyfish client adapter for container | ||
* To be used for testing jellyfish-core protocol data binding only | ||
*/ | ||
export class ContainerAdapterClient extends JellyfishClient { | ||
protected readonly container: DeFiDContainer | ||
|
||
constructor (container: DeFiDContainer) { | ||
super() | ||
this.container = container | ||
} | ||
|
||
/** | ||
* Wrap the call from client to testcontainers. | ||
*/ | ||
async call<T> (method: string, params: any[]): Promise<T> { | ||
const body = JSON.stringify({ | ||
jsonrpc: '1.0', | ||
id: Math.floor(Math.random() * 100000000000000), | ||
method: method, | ||
params: params | ||
}) | ||
|
||
const text = await this.container.post(body) | ||
const response = JSON.parse(text) | ||
|
||
const { result, error } = response | ||
|
||
if (error !== null) { | ||
throw new JellyfishError(error) | ||
} | ||
|
||
return result | ||
} | ||
} |
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 |
---|---|---|
@@ -1,5 +1,49 @@ | ||
import { getName } from '../src/core' | ||
import { MintingInfo, JellyfishError, JellyfishClient } from '../src/core' | ||
import { ContainerAdapterClient } from './container_adapter_client' | ||
import { RegTestContainer } from '@defichain/testcontainers' | ||
|
||
it('should getName jellyfish-core', () => { | ||
expect(getName()).toBe('jellyfish-core') | ||
class TestClient extends JellyfishClient { | ||
async call<T> (method: string, payload: any[]): Promise<T> { | ||
throw new JellyfishError({ | ||
code: 1, message: 'error message from node' | ||
}) | ||
} | ||
} | ||
|
||
it('should export client', async () => { | ||
const client = new TestClient() | ||
return await expect(client.call('fail', [])) | ||
.rejects.toThrowError(JellyfishError) | ||
}) | ||
|
||
it('should export categories', async () => { | ||
const client = new TestClient() | ||
return await expect(async () => { | ||
const info: MintingInfo = await client.mining.getMintingInfo() | ||
console.log(info) | ||
}).rejects.toThrowError(JellyfishError) | ||
}) | ||
|
||
describe('JellyfishError handling', () => { | ||
const container = new RegTestContainer() | ||
const client = new ContainerAdapterClient(container) | ||
|
||
beforeAll(async () => { | ||
await container.start() | ||
await container.waitForReady() | ||
}) | ||
|
||
afterAll(async () => { | ||
await container.stop() | ||
}) | ||
|
||
it('invalid method should throw -32601 with message as structured', async () => { | ||
return await expect(client.call('invalid', [])) | ||
.rejects.toThrowError(/JellyfishError from RPC: 'Method not found', code: -32601/) | ||
}) | ||
|
||
it('importprivkey should throw -5 with message as structured', async () => { | ||
return await expect(client.call('importprivkey', ['invalid-key'])) | ||
.rejects.toThrowError(/JellyfishError from RPC: 'Invalid private key encoding', code: -5/) | ||
}) | ||
}) |
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,41 @@ | ||
import { BigNumber } from '../src/core' | ||
|
||
/** | ||
* Why JavaScript default number should not be used as it lose precision | ||
*/ | ||
describe('number will lose precision', () => { | ||
it('1200000000.00000003 should not lose precision as a number but it does', () => { | ||
const dfi = 1200000000.00000003 | ||
|
||
expect(() => { | ||
expect(dfi.toString()).toBe('1200000000.00000003') | ||
}).toThrow() | ||
}) | ||
|
||
it('12.00000003 * 1000000 should be 12000000.03 but its not', () => { | ||
const obj = JSON.parse('{"dfi": 12.00000003}') | ||
const dfi = obj.dfi * 1000000 | ||
|
||
expect(() => { | ||
expect(dfi).toBe(12000000.03) | ||
}).toThrow() | ||
}) | ||
}) | ||
|
||
/** | ||
* BigNumber from 'bignumber.js' implementations will not lose precision | ||
*/ | ||
describe('BigNumber should not lose precision', () => { | ||
it('1200000000.00000003 should not lose precision', () => { | ||
const dfi = new BigNumber('1200000000.00000003') | ||
|
||
expect(dfi.toString()).toBe('1200000000.00000003') | ||
}) | ||
|
||
it('12.00000003 * 1000000 should be 12000000.03', () => { | ||
const obj = { dfi: new BigNumber('12.00000003') } | ||
const dfi = obj.dfi.multipliedBy(1000000) | ||
|
||
expect(dfi.toString()).toBe('12000000.03') | ||
}) | ||
}) |
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 |
---|---|---|
@@ -1,11 +1,12 @@ | ||
module.exports = { | ||
testEnvironment: 'node', | ||
testMatch: [ | ||
'**/__tests__/**/*.ts' | ||
'**/__tests__/**/*.test.ts' | ||
], | ||
transform: { | ||
'^.+\\.ts$': 'ts-jest' | ||
}, | ||
verbose: true, | ||
clearMocks: true | ||
clearMocks: true, | ||
testTimeout: 120000 | ||
} |
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
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,48 @@ | ||
import { JellyfishClient } from '../core' | ||
|
||
export interface MintingInfo { | ||
blocks: number | ||
currentblockweight?: number | ||
currentblocktx?: number | ||
difficulty: string | ||
isoperator: boolean | ||
masternodeid?: string | ||
masternodeoperator?: string | ||
masternodestate?: 'PRE_ENABLED' | 'ENABLED' | 'PRE_RESIGNED' | 'RESIGNED' | 'PRE_BANNED' | 'BANNED' | ||
generate?: boolean | ||
mintedblocks?: number | ||
networkhashps: number | ||
pooledtx: number | ||
chain: 'main' | 'test' | 'regtest' | string | ||
warnings: string | ||
} | ||
|
||
/** | ||
* Minting related RPC calls for DeFiChain | ||
*/ | ||
export class Mining { | ||
private readonly client: JellyfishClient | ||
|
||
constructor (client: JellyfishClient) { | ||
this.client = client | ||
} | ||
|
||
/** | ||
* Returns the estimated network hashes per second | ||
* | ||
* @param nblocks to estimate since last difficulty change. | ||
* @param height to estimate at the time of the given height. | ||
* @return Promise<number> | ||
*/ | ||
async getNetworkHashPerSecond (nblocks: number = 120, height: number = -1): Promise<number> { | ||
return await this.client.call('getnetworkhashps', [nblocks, height]) | ||
} | ||
|
||
/** | ||
* Get minting-related information | ||
* @return Promise<MintingInfo> | ||
*/ | ||
async getMintingInfo (): Promise<MintingInfo> { | ||
return await this.client.call('getmintinginfo', []) | ||
} | ||
} |
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 |
---|---|---|
@@ -1,3 +1,36 @@ | ||
export function getName (): string { | ||
return 'jellyfish-core' | ||
import BigNumber from 'bignumber.js' | ||
import { Mining } from './category/mining' | ||
|
||
// TODO(fuxingloh): might need to restructure how it's exported as this grows, will look into this soon | ||
export * from './category/mining' | ||
export { BigNumber } | ||
|
||
/** | ||
* JellyfishClient; a protocol agnostic DeFiChain node client, RPC calls are separated into their category. | ||
*/ | ||
export abstract class JellyfishClient { | ||
public readonly mining = new Mining(this) | ||
|
||
/** | ||
* A promise based procedure call handling | ||
* | ||
* @param method name | ||
* @param params payload | ||
* @throws JellyfishError | ||
*/ | ||
abstract call<T> (method: string, params: any[]): Promise<T> | ||
} | ||
|
||
/** | ||
* JellyfishError; where jellyfish/defichain errors are encapsulated into. | ||
*/ | ||
export class JellyfishError extends Error { | ||
public readonly code: number | ||
public readonly rawMessage: string | ||
|
||
constructor (error: { code: number, message: string }) { | ||
super(`JellyfishError from RPC: '${error.message}', code: ${error.code}`) | ||
this.code = error.code | ||
this.rawMessage = error.message | ||
} | ||
} |
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.