mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
feat(mining): Ported mining endpoints from bch-api
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
REST API Controller for the /full-node/mining routes.
|
||||
*/
|
||||
|
||||
import wlogger from '../../../../adapters/wlogger.js'
|
||||
|
||||
class MiningRESTController {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Mining REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases || !this.useCases.mining) {
|
||||
throw new Error(
|
||||
'Instance of Mining use cases required when instantiating Mining REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
this.miningUseCases = this.useCases.mining
|
||||
|
||||
// Bind functions
|
||||
this.root = this.root.bind(this)
|
||||
this.getMiningInfo = this.getMiningInfo.bind(this)
|
||||
this.getNetworkHashPS = this.getNetworkHashPS.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /v6/full-node/mining/ Service status
|
||||
* @apiName MiningRoot
|
||||
* @apiGroup Mining
|
||||
*
|
||||
* @apiDescription Returns the status of the mining service.
|
||||
*
|
||||
* @apiSuccess {String} status Service identifier
|
||||
*/
|
||||
async root (req, res) {
|
||||
return res.status(200).json({ status: 'mining' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /v6/full-node/mining/getMiningInfo Get Mining Info
|
||||
* @apiName GetMiningInfo
|
||||
* @apiGroup Mining
|
||||
* @apiDescription Returns a json object containing mining-related information.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getMiningInfo" -H "accept: application/json"
|
||||
*/
|
||||
async getMiningInfo (req, res) {
|
||||
try {
|
||||
const result = await this.miningUseCases.getMiningInfo()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /v6/full-node/mining/getNetworkHashPS Get Estimated network hashes per second
|
||||
* @apiName GetNetworkHashPS
|
||||
* @apiGroup Mining
|
||||
* @apiDescription Returns the estimated network hashes per second based on the last n blocks. Pass in [nblocks] to override # of blocks, -1 specifies since last difficulty change. Pass in [height] to estimate the network speed at the time when a certain block was found.
|
||||
*
|
||||
* @apiParam {Number} nblocks Number of blocks to use for estimation (default: 120)
|
||||
* @apiParam {Number} height Block height to estimate at (default: -1)
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v6/full-node/mining/getNetworkHashPS?nblocks=120&height=-1" -H "accept: application/json"
|
||||
*/
|
||||
async getNetworkHashPS (req, res) {
|
||||
try {
|
||||
let nblocks = 120 // Default
|
||||
let height = -1 // Default
|
||||
if (req.query.nblocks) nblocks = parseInt(req.query.nblocks)
|
||||
if (req.query.height) height = parseInt(req.query.height)
|
||||
|
||||
const result = await this.miningUseCases.getNetworkHashPS({ nblocks, height })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
handleError (err, res) {
|
||||
wlogger.error('Error in MiningRESTController:', err)
|
||||
|
||||
const status = err.status || 500
|
||||
const message = err.message || 'Internal server error'
|
||||
|
||||
return res.status(status).json({ error: message })
|
||||
}
|
||||
}
|
||||
|
||||
export default MiningRESTController
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
REST API router for /full-node/mining routes.
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import MiningRESTController from './controller.js'
|
||||
|
||||
class MiningRouter {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Mining REST Router.'
|
||||
)
|
||||
}
|
||||
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Mining REST Router.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
this.miningController = new MiningRESTController(dependencies)
|
||||
|
||||
this.apiPrefix = (localConfig.apiPrefix || '').replace(/\/$/, '')
|
||||
this.baseUrl = `${this.apiPrefix}/full-node/mining`
|
||||
if (!this.baseUrl.startsWith('/')) {
|
||||
this.baseUrl = `/${this.baseUrl}`
|
||||
}
|
||||
this.router = express.Router()
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error('Must pass app object when attaching REST API controllers.')
|
||||
}
|
||||
|
||||
this.router.get('/', this.miningController.root)
|
||||
this.router.get('/getMiningInfo', this.miningController.getMiningInfo)
|
||||
this.router.get('/getNetworkHashPS', this.miningController.getNetworkHashPS)
|
||||
|
||||
app.use(this.baseUrl, this.router)
|
||||
}
|
||||
}
|
||||
|
||||
export default MiningRouter
|
||||
@@ -10,6 +10,7 @@
|
||||
import BlockchainRouter from './full-node/blockchain/index.js'
|
||||
import ControlRouter from './full-node/control/index.js'
|
||||
import DSProofRouter from './full-node/dsproof/index.js'
|
||||
import MiningRouter from './full-node/mining/index.js'
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
@@ -64,6 +65,9 @@ class RESTControllers {
|
||||
|
||||
const dsproofRouter = new DSProofRouter(dependencies)
|
||||
dsproofRouter.attach(app)
|
||||
|
||||
const miningRouter = new MiningRouter(dependencies)
|
||||
miningRouter.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Use cases for interacting with the BCH full node mining RPC interface.
|
||||
*/
|
||||
|
||||
class MiningUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
|
||||
if (!this.adapters) {
|
||||
throw new Error('Adapters instance required when instantiating Mining use cases.')
|
||||
}
|
||||
|
||||
this.fullNode = this.adapters.fullNode
|
||||
if (!this.fullNode) {
|
||||
throw new Error('Full node adapter required when instantiating Mining use cases.')
|
||||
}
|
||||
}
|
||||
|
||||
async getMiningInfo () {
|
||||
return this.fullNode.call('getmininginfo')
|
||||
}
|
||||
|
||||
async getNetworkHashPS ({ nblocks, height }) {
|
||||
return this.fullNode.call('getnetworkhashps', [nblocks, height])
|
||||
}
|
||||
}
|
||||
|
||||
export default MiningUseCases
|
||||
@@ -8,6 +8,7 @@
|
||||
import BlockchainUseCases from './full-node-blockchain-use-cases.js'
|
||||
import ControlUseCases from './full-node-control-use-cases.js'
|
||||
import DSProofUseCases from './full-node-dsproof-use-cases.js'
|
||||
import MiningUseCases from './full-node-mining-use-cases.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -21,6 +22,7 @@ class UseCases {
|
||||
this.blockchain = new BlockchainUseCases({ adapters: this.adapters })
|
||||
this.control = new ControlUseCases({ adapters: this.adapters })
|
||||
this.dsproof = new DSProofUseCases({ adapters: this.adapters })
|
||||
this.mining = new MiningUseCases({ adapters: this.adapters })
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
Unit tests for MiningRESTController.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import MiningRESTController from '../../../src/controllers/rest-api/full-node/mining/controller.js'
|
||||
import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js'
|
||||
|
||||
describe('#mining-controller.js', () => {
|
||||
let sandbox
|
||||
let mockAdapters
|
||||
let mockUseCases
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
mockAdapters = {}
|
||||
mockUseCases = {
|
||||
mining: {
|
||||
getMiningInfo: sandbox.stub().resolves({ blocks: 100, difficulty: 1.5 }),
|
||||
getNetworkHashPS: sandbox.stub().resolves(1234567890)
|
||||
}
|
||||
}
|
||||
|
||||
uut = new MiningRESTController({
|
||||
adapters: mockAdapters,
|
||||
useCases: mockUseCases
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new MiningRESTController({ useCases: mockUseCases })
|
||||
}, /Adapters library required/)
|
||||
})
|
||||
|
||||
it('should require mining use cases', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new MiningRESTController({ adapters: mockAdapters, useCases: {} })
|
||||
}, /Mining use cases required/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#root()', () => {
|
||||
it('should return mining status', async () => {
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.root(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { status: 'mining' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getMiningInfo()', () => {
|
||||
it('should return mining info on success', async () => {
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getMiningInfo(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { blocks: 100, difficulty: 1.5 })
|
||||
assert.isTrue(mockUseCases.mining.getMiningInfo.calledOnce)
|
||||
})
|
||||
|
||||
it('should handle errors via handleError', async () => {
|
||||
const error = new Error('failure')
|
||||
error.status = 503
|
||||
mockUseCases.mining.getMiningInfo.rejects(error)
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getMiningInfo(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 503)
|
||||
assert.deepEqual(res.jsonData, { error: 'failure' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getNetworkHashPS()', () => {
|
||||
it('should return network hash PS with default params', async () => {
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getNetworkHashPS(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.equal(res.jsonData, 1234567890)
|
||||
assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce)
|
||||
assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], {
|
||||
nblocks: 120,
|
||||
height: -1
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse query params for nblocks and height', async () => {
|
||||
const req = createMockRequest({
|
||||
query: {
|
||||
nblocks: '240',
|
||||
height: '1000'
|
||||
}
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getNetworkHashPS(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce)
|
||||
assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], {
|
||||
nblocks: 240,
|
||||
height: 1000
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors via handleError', async () => {
|
||||
const error = new Error('RPC error')
|
||||
error.status = 500
|
||||
mockUseCases.mining.getNetworkHashPS.rejects(error)
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getNetworkHashPS(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 500)
|
||||
assert.deepEqual(res.jsonData, { error: 'RPC error' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,7 @@ import RESTControllers from '../../../src/controllers/rest-api/index.js'
|
||||
import BlockchainRouter from '../../../src/controllers/rest-api/full-node/blockchain/index.js'
|
||||
import ControlRouter from '../../../src/controllers/rest-api/full-node/control/index.js'
|
||||
import DSProofRouter from '../../../src/controllers/rest-api/full-node/dsproof/index.js'
|
||||
import MiningRouter from '../../../src/controllers/rest-api/full-node/mining/index.js'
|
||||
|
||||
describe('#controllers/rest-api/index.js', () => {
|
||||
let sandbox
|
||||
@@ -51,6 +52,10 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
},
|
||||
dsproof: {
|
||||
getDSProof: () => {}
|
||||
},
|
||||
mining: {
|
||||
getMiningInfo: () => {},
|
||||
getNetworkHashPS: () => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -80,6 +85,7 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
const blockchainAttachStub = sandbox.stub(BlockchainRouter.prototype, 'attach')
|
||||
const controlAttachStub = sandbox.stub(ControlRouter.prototype, 'attach')
|
||||
const dsproofAttachStub = sandbox.stub(DSProofRouter.prototype, 'attach')
|
||||
const miningAttachStub = sandbox.stub(MiningRouter.prototype, 'attach')
|
||||
const restControllers = new RESTControllers({
|
||||
adapters: mockAdapters,
|
||||
useCases: mockUseCases
|
||||
@@ -94,6 +100,8 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
assert.equal(controlAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(dsproofAttachStub.calledOnce)
|
||||
assert.equal(dsproofAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(miningAttachStub.calledOnce)
|
||||
assert.equal(miningAttachStub.getCall(0).args[0], app)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Unit tests for MiningUseCases.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
import MiningUseCases from '../../../src/use-cases/full-node-mining-use-cases.js'
|
||||
|
||||
describe('#full-node-mining-use-cases.js', () => {
|
||||
let mockAdapters
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
mockAdapters = {
|
||||
fullNode: {
|
||||
call: async () => ({})
|
||||
}
|
||||
}
|
||||
|
||||
uut = new MiningUseCases({ adapters: mockAdapters })
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new MiningUseCases()
|
||||
}, /Adapters instance required/)
|
||||
})
|
||||
|
||||
it('should require full node adapter', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new MiningUseCases({ adapters: {} })
|
||||
}, /Full node adapter required/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getMiningInfo()', () => {
|
||||
it('should call full node adapter with correct method', async () => {
|
||||
let capturedMethod = ''
|
||||
mockAdapters.fullNode.call = async method => {
|
||||
capturedMethod = method
|
||||
return { blocks: 100, difficulty: 1.5 }
|
||||
}
|
||||
|
||||
const result = await uut.getMiningInfo()
|
||||
|
||||
assert.equal(capturedMethod, 'getmininginfo')
|
||||
assert.deepEqual(result, { blocks: 100, difficulty: 1.5 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getNetworkHashPS()', () => {
|
||||
it('should call full node adapter with correct method and default params', async () => {
|
||||
let capturedMethod = ''
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedMethod = method
|
||||
capturedParams = params
|
||||
return 1234567890
|
||||
}
|
||||
|
||||
const result = await uut.getNetworkHashPS({ nblocks: 120, height: -1 })
|
||||
|
||||
assert.equal(capturedMethod, 'getnetworkhashps')
|
||||
assert.deepEqual(capturedParams, [120, -1])
|
||||
assert.equal(result, 1234567890)
|
||||
})
|
||||
|
||||
it('should call full node adapter with custom params', async () => {
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedParams = params
|
||||
return 9876543210
|
||||
}
|
||||
|
||||
const result = await uut.getNetworkHashPS({ nblocks: 240, height: 1000 })
|
||||
|
||||
assert.deepEqual(capturedParams, [240, 1000])
|
||||
assert.equal(result, 9876543210)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user