mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api.git
synced 2026-09-21 16:52:00 -07:00
feat(blockchain): Adding blockchain full-node endpoints
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Full Node Connection
|
||||
RPC_BASEURL=http://172.17.0.1:8332
|
||||
RPC_USERNAME=bitcoin
|
||||
RPC_PASSWORD=password
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "REST2NOSTR Proxy API",
|
||||
"name": "psf-bch-api REST API",
|
||||
"version": "1.0.0",
|
||||
"description": "REST API proxy for Nostr WebSocket protocol",
|
||||
"title": "REST2NOSTR Proxy API",
|
||||
"url": "https://nostr-relay-api.psfoundation.info",
|
||||
"sampleUrl": "https://nostr-relay-api.psfoundation.info"
|
||||
"description": "REST API proxy to Bitcoin Cash infrastructure",
|
||||
"title": "psf-bch-api REST API",
|
||||
"url": "http://localhost:5942",
|
||||
"sampleUrl": "http://localhost:5942"
|
||||
}
|
||||
|
||||
|
||||
Generated
+102
-2
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.3.1",
|
||||
"express": "5.1.0",
|
||||
@@ -1469,6 +1470,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||
@@ -1485,6 +1492,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -1938,6 +1956,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||
@@ -2156,6 +2186,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -2458,7 +2497,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -3352,6 +3390,26 @@
|
||||
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
@@ -3385,6 +3443,43 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -3759,7 +3854,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
@@ -5849,6 +5943,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"license": "MIT",
|
||||
"description": "REST API proxy to Bitcoin Cash infrastructure",
|
||||
"dependencies": {
|
||||
"axios": "1.7.7",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.3.1",
|
||||
"express": "5.1.0",
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
Adapter library for interacting with a BCH full node over JSON-RPC.
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import wlogger from './wlogger.js'
|
||||
import config from '../config/index.js'
|
||||
|
||||
class FullNodeRPCAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
this.config = localConfig.config || config
|
||||
|
||||
if (!this.config.fullNode || !this.config.fullNode.rpcBaseUrl) {
|
||||
throw new Error('Full node RPC configuration is required')
|
||||
}
|
||||
|
||||
const {
|
||||
rpcBaseUrl,
|
||||
rpcUsername,
|
||||
rpcPassword,
|
||||
rpcTimeoutMs = 15000
|
||||
} = this.config.fullNode
|
||||
console.log('this.config.fullNode', this.config.fullNode)
|
||||
|
||||
this.requestIdPrefix = this.config.fullNode.rpcRequestIdPrefix || 'psf-bch-api'
|
||||
|
||||
this.http = axios.create({
|
||||
baseURL: rpcBaseUrl,
|
||||
timeout: rpcTimeoutMs,
|
||||
auth: {
|
||||
username: rpcUsername,
|
||||
password: rpcPassword
|
||||
}
|
||||
})
|
||||
|
||||
this.defaultRequestPayload = {
|
||||
jsonrpc: '1.0'
|
||||
}
|
||||
}
|
||||
|
||||
async call (method, params = [], requestId) {
|
||||
const id = requestId || `${this.requestIdPrefix}-${method}`
|
||||
|
||||
try {
|
||||
const response = await this.http.post('', {
|
||||
...this.defaultRequestPayload,
|
||||
id,
|
||||
method,
|
||||
params
|
||||
})
|
||||
|
||||
if (response.data && response.data.error) {
|
||||
const rpcError = this._formatError(response.data.error.message, 400)
|
||||
throw rpcError
|
||||
}
|
||||
|
||||
return response.data.result
|
||||
} catch (err) {
|
||||
throw this._handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
_handleError (err) {
|
||||
const { status, message } = this.decodeError(err)
|
||||
const error = new Error(message)
|
||||
error.status = status
|
||||
error.originalError = err
|
||||
return error
|
||||
}
|
||||
|
||||
decodeError (err) {
|
||||
try {
|
||||
if (
|
||||
err.response &&
|
||||
err.response.data &&
|
||||
err.response.data.error &&
|
||||
err.response.data.error.message
|
||||
) {
|
||||
return this._formatError(err.response.data.error.message, 400)
|
||||
}
|
||||
|
||||
if (err.response && err.response.data) {
|
||||
return this._formatError(err.response.data, err.response.status || 500)
|
||||
}
|
||||
|
||||
if (err.message) {
|
||||
if (err.message.includes('ENOTFOUND') || err.message.includes('ENETUNREACH') || err.message.includes('EAI_AGAIN')) {
|
||||
return this._formatError(
|
||||
'Network error: Could not communicate with full node or other external service.',
|
||||
503
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (err.code && (err.code === 'ECONNABORTED' || err.code === 'ECONNREFUSED')) {
|
||||
return this._formatError(
|
||||
'Network error: Could not communicate with full node or other external service.',
|
||||
503
|
||||
)
|
||||
}
|
||||
|
||||
if (err.error && typeof err.error === 'string' && err.error.includes('429')) {
|
||||
return this._formatError('429 Too Many Requests', 429)
|
||||
}
|
||||
|
||||
if (err.message) {
|
||||
return this._formatError(err.message, err.status || 422)
|
||||
}
|
||||
|
||||
return this._formatError('Unhandled full node error', 500)
|
||||
} catch (decodeError) {
|
||||
wlogger.error('Unhandled error in FullNodeRPCAdapter.decodeError()', decodeError)
|
||||
return this._formatError('Internal server error', 500)
|
||||
}
|
||||
}
|
||||
|
||||
validateArraySize (length, options = {}) {
|
||||
const { isProUser = false } = options
|
||||
const freemiumLimit = Number(this.config.fullNode?.freemiumArrayLimit || 20)
|
||||
const proLimit = Number(this.config.fullNode?.proArrayLimit || freemiumLimit)
|
||||
|
||||
const limit = isProUser ? proLimit : freemiumLimit
|
||||
return length <= limit
|
||||
}
|
||||
|
||||
_formatError (message, status = 500) {
|
||||
return {
|
||||
message: message || 'Internal server error',
|
||||
status: status || 500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FullNodeRPCAdapter
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
// Load individual adapter libraries.
|
||||
// import NostrRelayAdapter from './nostr-relay.js'
|
||||
import FullNodeRPCAdapter from './full-node-rpc.js'
|
||||
import config from '../config/index.js'
|
||||
|
||||
class Adapters {
|
||||
@@ -30,6 +31,8 @@ class Adapters {
|
||||
// Maintain backward compatibility: expose first relay as nostrRelay
|
||||
// This allows existing code to work during transition
|
||||
// this.nostrRelay = this.nostrRelays[0]
|
||||
|
||||
this.fullNode = new FullNodeRPCAdapter({ config: this.config })
|
||||
}
|
||||
|
||||
async start () {
|
||||
|
||||
Vendored
+12
@@ -3,10 +3,13 @@
|
||||
environments.
|
||||
*/
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
import { readFileSync } from 'fs'
|
||||
dotenv.config()
|
||||
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../../package.json`))
|
||||
@@ -47,6 +50,15 @@ export default {
|
||||
return ['wss://nostr-relay.psfoundation.info', 'wss://relay.damus.io']
|
||||
})(),
|
||||
|
||||
// Full node RPC configuration
|
||||
fullNode: {
|
||||
rpcBaseUrl: process.env.RPC_BASEURL || 'http://127.0.0.1:8332',
|
||||
rpcUsername: process.env.RPC_USERNAME || '',
|
||||
rpcPassword: process.env.RPC_PASSWORD || '',
|
||||
rpcTimeoutMs: Number(process.env.RPC_TIMEOUT_MS || 15000),
|
||||
rpcRequestIdPrefix: process.env.RPC_REQUEST_ID_PREFIX || 'psf-bch-api'
|
||||
},
|
||||
|
||||
// Version
|
||||
version
|
||||
}
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
REST API Controller for the /full-node/blockchain routes.
|
||||
*/
|
||||
|
||||
import wlogger from '../../../../adapters/wlogger.js'
|
||||
|
||||
class BlockchainRESTController {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Blockchain REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases || !this.useCases.blockchain) {
|
||||
throw new Error(
|
||||
'Instance of Blockchain use cases required when instantiating Blockchain REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
this.blockchainUseCases = this.useCases.blockchain
|
||||
|
||||
// Bind functions
|
||||
this.root = this.root.bind(this)
|
||||
this.getBestBlockHash = this.getBestBlockHash.bind(this)
|
||||
this.getBlockchainInfo = this.getBlockchainInfo.bind(this)
|
||||
this.getBlockCount = this.getBlockCount.bind(this)
|
||||
this.getBlockHeaderSingle = this.getBlockHeaderSingle.bind(this)
|
||||
this.getBlockHeaderBulk = this.getBlockHeaderBulk.bind(this)
|
||||
this.getChainTips = this.getChainTips.bind(this)
|
||||
this.getDifficulty = this.getDifficulty.bind(this)
|
||||
this.getMempoolEntrySingle = this.getMempoolEntrySingle.bind(this)
|
||||
this.getMempoolEntryBulk = this.getMempoolEntryBulk.bind(this)
|
||||
this.getMempoolAncestorsSingle = this.getMempoolAncestorsSingle.bind(this)
|
||||
this.getMempoolInfo = this.getMempoolInfo.bind(this)
|
||||
this.getRawMempool = this.getRawMempool.bind(this)
|
||||
this.getTxOut = this.getTxOut.bind(this)
|
||||
this.getTxOutPost = this.getTxOutPost.bind(this)
|
||||
this.getTxOutProofSingle = this.getTxOutProofSingle.bind(this)
|
||||
this.getTxOutProofBulk = this.getTxOutProofBulk.bind(this)
|
||||
this.verifyTxOutProofSingle = this.verifyTxOutProofSingle.bind(this)
|
||||
this.verifyTxOutProofBulk = this.verifyTxOutProofBulk.bind(this)
|
||||
this.getBlock = this.getBlock.bind(this)
|
||||
this.getBlockHash = this.getBlockHash.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/ Service status
|
||||
* @apiName BlockchainRoot
|
||||
* @apiGroup Blockchain
|
||||
*
|
||||
* @apiDescription Returns the status of the blockchain service.
|
||||
*
|
||||
* @apiSuccess {String} status Service identifier
|
||||
*/
|
||||
async root (req, res) {
|
||||
return res.status(200).json({ status: 'blockchain' })
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getBestBlockHash Get best block hash
|
||||
* @apiName GetBestBlockHash
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the hash of the best (tip) block in the longest block chain.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v5/blockchain/getBestBlockHash" -H "accept: application/json"
|
||||
*
|
||||
* @apiSuccess {String} bestBlockHash Hash of the best block
|
||||
*/
|
||||
async getBestBlockHash (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getBestBlockHash()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getBlockchainInfo Get blockchain info
|
||||
* @apiName GetBlockchainInfo
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns various state info regarding blockchain processing.
|
||||
*/
|
||||
async getBlockchainInfo (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getBlockchainInfo()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getBlockCount Get block count
|
||||
* @apiName GetBlockCount
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the number of blocks in the longest blockchain.
|
||||
*/
|
||||
async getBlockCount (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getBlockCount()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getBlockHeader/:hash Get single block header
|
||||
* @apiName GetSingleBlockHeader
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns serialized block header data.
|
||||
*
|
||||
* @apiParam {String} hash Block hash
|
||||
* @apiParam {Boolean} verbose Return verbose data (default false)
|
||||
*/
|
||||
async getBlockHeaderSingle (req, res) {
|
||||
try {
|
||||
const hash = req.params.hash
|
||||
if (!hash) {
|
||||
return res.status(400).json({ error: 'hash can not be empty' })
|
||||
}
|
||||
|
||||
const verbose = req.query.verbose?.toString() === 'true'
|
||||
const result = await this.blockchainUseCases.getBlockHeader({ hash, verbose })
|
||||
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/getBlockHeader Get multiple block headers
|
||||
* @apiName GetBulkBlockHeader
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns serialized block header data for multiple hashes.
|
||||
*
|
||||
* @apiParam {String[]} hashes Block hashes
|
||||
* @apiParam {Boolean} verbose Return verbose data (default false)
|
||||
*/
|
||||
async getBlockHeaderBulk (req, res) {
|
||||
try {
|
||||
const hashes = req.body.hashes
|
||||
const verbose = !!req.body.verbose
|
||||
|
||||
if (!Array.isArray(hashes)) {
|
||||
return res.status(400).json({
|
||||
error: 'hashes needs to be an array. Use GET for single hash.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.adapters.fullNode.validateArraySize(hashes.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
||||
return res.status(400).json({ error: 'Array too large.' })
|
||||
}
|
||||
|
||||
for (const hash of hashes) {
|
||||
if (!hash || hash.length !== 64) {
|
||||
return res.status(400).json({ error: `This is not a hash: ${hash}` })
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getBlockHeaders({ hashes, verbose })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getChainTips Get chain tips
|
||||
* @apiName GetChainTips
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns information about known tips in the block tree.
|
||||
*/
|
||||
async getChainTips (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getChainTips()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getDifficulty Get difficulty
|
||||
* @apiName GetDifficulty
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the current difficulty value.
|
||||
*/
|
||||
async getDifficulty (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getDifficulty()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getMempoolEntry/:txid Get single mempool entry
|
||||
* @apiName GetMempoolEntry
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool data for a transaction.
|
||||
*/
|
||||
async getMempoolEntrySingle (req, res) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid) {
|
||||
return res.status(400).json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getMempoolEntry({ txid })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/getMempoolEntry Get bulk mempool entry
|
||||
* @apiName GetMempoolEntryBulk
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool data for multiple transactions.
|
||||
*/
|
||||
async getMempoolEntryBulk (req, res) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
return res.status(400).json({
|
||||
error: 'txids needs to be an array. Use GET for single txid.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.adapters.fullNode.validateArraySize(txids.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
||||
return res.status(400).json({ error: 'Array too large.' })
|
||||
}
|
||||
|
||||
for (const txid of txids) {
|
||||
if (!txid || txid.length !== 64) {
|
||||
return res.status(400).json({ error: 'This is not a txid' })
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getMempoolEntries({ txids })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getMempoolAncestors/:txid Get mempool ancestors
|
||||
* @apiName GetMempoolAncestors
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns mempool ancestor data for a transaction.
|
||||
*/
|
||||
async getMempoolAncestorsSingle (req, res) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid) {
|
||||
return res.status(400).json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
let verbose = false
|
||||
if (req.query.verbose && req.query.verbose.toString() === 'true') {
|
||||
verbose = true
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getMempoolAncestors({ txid, verbose })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getMempoolInfo Get mempool info
|
||||
* @apiName GetMempoolInfo
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details on the state of the mempool.
|
||||
*/
|
||||
async getMempoolInfo (req, res) {
|
||||
try {
|
||||
const result = await this.blockchainUseCases.getMempoolInfo()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getRawMempool Get raw mempool
|
||||
* @apiName GetRawMempool
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns all transaction ids in the mempool.
|
||||
*
|
||||
* @apiParam {Boolean} verbose Return verbose data (default false)
|
||||
*/
|
||||
async getRawMempool (req, res) {
|
||||
try {
|
||||
const verbose = req.query.verbose === 'true'
|
||||
const result = await this.blockchainUseCases.getRawMempool({ verbose })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getTxOut/:txid/:n Get transaction output
|
||||
* @apiName GetTxOut
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details about an unspent transaction output.
|
||||
*/
|
||||
async getTxOut (req, res) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid) {
|
||||
return res.status(400).json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
const nRaw = req.params.n
|
||||
if (nRaw === undefined || nRaw === '') {
|
||||
return res.status(400).json({ error: 'n can not be empty' })
|
||||
}
|
||||
|
||||
const n = parseInt(nRaw)
|
||||
const includeMempool = req.query.includeMempool === 'true'
|
||||
|
||||
const result = await this.blockchainUseCases.getTxOut({
|
||||
txid,
|
||||
n,
|
||||
includeMempool
|
||||
})
|
||||
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/getTxOut Validate a UTXO
|
||||
* @apiName GetTxOutPost
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details about an unspent transaction output.
|
||||
*/
|
||||
async getTxOutPost (req, res) {
|
||||
try {
|
||||
const txid = req.body.txid
|
||||
if (!txid) {
|
||||
return res.status(400).json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
const voutRaw = req.body.vout
|
||||
if (voutRaw === undefined || voutRaw === '') {
|
||||
return res.status(400).json({ error: 'vout can not be empty' })
|
||||
}
|
||||
|
||||
const n = parseInt(voutRaw)
|
||||
const mempool = req.body.mempool !== undefined ? !!req.body.mempool : true
|
||||
|
||||
const result = await this.blockchainUseCases.getTxOut({
|
||||
txid,
|
||||
n,
|
||||
includeMempool: mempool
|
||||
})
|
||||
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getTxOutProof/:txid Get TxOut proof
|
||||
* @apiName GetTxOutProofSingle
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns a hex-encoded proof that the transaction was included in a block.
|
||||
*/
|
||||
async getTxOutProofSingle (req, res) {
|
||||
try {
|
||||
const txid = req.params.txid
|
||||
if (!txid) {
|
||||
return res.status(400).json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getTxOutProof({ txid })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/getTxOutProof Get TxOut proofs
|
||||
* @apiName GetTxOutProofBulk
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns hex-encoded proofs for transactions.
|
||||
*/
|
||||
async getTxOutProofBulk (req, res) {
|
||||
try {
|
||||
const txids = req.body.txids
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
return res.status(400).json({
|
||||
error: 'txids needs to be an array. Use GET for single txid.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.adapters.fullNode.validateArraySize(txids.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
||||
return res.status(400).json({ error: 'Array too large.' })
|
||||
}
|
||||
|
||||
for (const txid of txids) {
|
||||
if (!txid || txid.length !== 64) {
|
||||
return res.status(400).json({
|
||||
error: `Invalid txid. Double check your txid is valid: ${txid}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getTxOutProofs({ txids })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/verifyTxOutProof/:proof Verify TxOut proof
|
||||
* @apiName VerifyTxOutProofSingle
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Verifies a hex-encoded proof was included in a block.
|
||||
*/
|
||||
async verifyTxOutProofSingle (req, res) {
|
||||
try {
|
||||
const proof = req.params.proof
|
||||
if (!proof) {
|
||||
return res.status(400).json({ error: 'proof can not be empty' })
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.verifyTxOutProof({ proof })
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/verifyTxOutProof Verify TxOut proofs
|
||||
* @apiName VerifyTxOutProofBulk
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Verifies hex-encoded proofs were included in blocks.
|
||||
*/
|
||||
async verifyTxOutProofBulk (req, res) {
|
||||
try {
|
||||
const proofs = req.body.proofs
|
||||
|
||||
if (!Array.isArray(proofs)) {
|
||||
return res.status(400).json({
|
||||
error: 'proofs needs to be an array. Use GET for single proof.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!this.adapters.fullNode.validateArraySize(proofs.length, { isProUser: Boolean(req.locals?.proLimit) })) {
|
||||
return res.status(400).json({ error: 'Array too large.' })
|
||||
}
|
||||
|
||||
for (const proof of proofs) {
|
||||
if (!proof) {
|
||||
return res.status(400).json({ error: `proof can not be empty: ${proof}` })
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.verifyTxOutProofs({ proofs })
|
||||
const flattened = result.map(entry => Array.isArray(entry) ? entry[0] : entry)
|
||||
|
||||
return res.status(200).json(flattened)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /full-node/blockchain/getBlock Get block details
|
||||
* @apiName GetBlock
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns block details for a hash.
|
||||
*/
|
||||
async getBlock (req, res) {
|
||||
try {
|
||||
const blockhash = req.body.blockhash
|
||||
if (!blockhash) {
|
||||
return res.status(400).json({ error: 'blockhash can not be empty' })
|
||||
}
|
||||
|
||||
let verbosity = req.body.verbosity
|
||||
if (verbosity === undefined || verbosity === null) {
|
||||
verbosity = 1
|
||||
}
|
||||
|
||||
const result = await this.blockchainUseCases.getBlock({
|
||||
blockhash,
|
||||
verbosity
|
||||
})
|
||||
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /full-node/blockchain/getBlockHash/:height Get block hash
|
||||
* @apiName GetBlockHash
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns the hash of a block by height.
|
||||
*/
|
||||
async getBlockHash (req, res) {
|
||||
try {
|
||||
const heightRaw = req.params.height
|
||||
if (!heightRaw) {
|
||||
return res.status(400).json({ error: 'height can not be empty' })
|
||||
}
|
||||
|
||||
const height = parseInt(heightRaw)
|
||||
const result = await this.blockchainUseCases.getBlockHash({ height })
|
||||
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
return this.handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
handleError (err, res) {
|
||||
wlogger.error('Error in BlockchainRESTController:', err)
|
||||
|
||||
const status = err.status || 500
|
||||
const message = err.message || 'Internal server error'
|
||||
|
||||
return res.status(status).json({ error: message })
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockchainRESTController
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
REST API router for /full-node/blockchain routes.
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import BlockchainRESTController from './controller.js'
|
||||
|
||||
class BlockchainRouter {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Blockchain REST Router.'
|
||||
)
|
||||
}
|
||||
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Blockchain REST Router.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
this.blockchainController = new BlockchainRESTController(dependencies)
|
||||
|
||||
this.baseUrl = '/full-node/blockchain'
|
||||
this.router = express.Router()
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error('Must pass app object when attaching REST API controllers.')
|
||||
}
|
||||
|
||||
this.router.get('/', this.blockchainController.root)
|
||||
this.router.get('/getBestBlockHash', this.blockchainController.getBestBlockHash)
|
||||
this.router.get('/getBlockchainInfo', this.blockchainController.getBlockchainInfo)
|
||||
this.router.get('/getBlockCount', this.blockchainController.getBlockCount)
|
||||
this.router.get('/getBlockHeader/:hash', this.blockchainController.getBlockHeaderSingle)
|
||||
this.router.post('/getBlockHeader', this.blockchainController.getBlockHeaderBulk)
|
||||
this.router.get('/getChainTips', this.blockchainController.getChainTips)
|
||||
this.router.get('/getDifficulty', this.blockchainController.getDifficulty)
|
||||
this.router.get('/getMempoolEntry/:txid', this.blockchainController.getMempoolEntrySingle)
|
||||
this.router.post('/getMempoolEntry', this.blockchainController.getMempoolEntryBulk)
|
||||
this.router.get('/getMempoolAncestors/:txid', this.blockchainController.getMempoolAncestorsSingle)
|
||||
this.router.get('/getMempoolInfo', this.blockchainController.getMempoolInfo)
|
||||
this.router.get('/getRawMempool', this.blockchainController.getRawMempool)
|
||||
this.router.get('/getTxOut/:txid/:n', this.blockchainController.getTxOut)
|
||||
this.router.post('/getTxOut', this.blockchainController.getTxOutPost)
|
||||
this.router.get('/getTxOutProof/:txid', this.blockchainController.getTxOutProofSingle)
|
||||
this.router.post('/getTxOutProof', this.blockchainController.getTxOutProofBulk)
|
||||
this.router.get('/verifyTxOutProof/:proof', this.blockchainController.verifyTxOutProofSingle)
|
||||
this.router.post('/verifyTxOutProof', this.blockchainController.verifyTxOutProofBulk)
|
||||
this.router.post('/getBlock', this.blockchainController.getBlock)
|
||||
this.router.get('/getBlockHash/:height', this.blockchainController.getBlockHash)
|
||||
|
||||
app.use(this.baseUrl, this.router)
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockchainRouter
|
||||
@@ -7,6 +7,7 @@
|
||||
// Local libraries
|
||||
// import EventRouter from './event/index.js'
|
||||
// import ReqRouter from './req/index.js'
|
||||
import BlockchainRouter from './full-node/blockchain/index.js'
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
@@ -33,10 +34,10 @@ class RESTControllers {
|
||||
}
|
||||
|
||||
attachRESTControllers (app) {
|
||||
// const dependencies = {
|
||||
// adapters: this.adapters,
|
||||
// useCases: this.useCases
|
||||
// }
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /event route
|
||||
// const eventRouter = new EventRouter(dependencies)
|
||||
@@ -45,6 +46,9 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /req route
|
||||
// const reqRouter = new ReqRouter(dependencies)
|
||||
// reqRouter.attach(app)
|
||||
|
||||
const blockchainRouter = new BlockchainRouter(dependencies)
|
||||
blockchainRouter.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
Use cases for interacting with the BCH full node blockchain RPC interface.
|
||||
*/
|
||||
|
||||
import wlogger from '../../adapters/wlogger.js'
|
||||
|
||||
class BlockchainUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = localConfig.adapters
|
||||
|
||||
if (!this.adapters) {
|
||||
throw new Error('Adapters instance required when instantiating Blockchain use cases.')
|
||||
}
|
||||
|
||||
this.fullNode = this.adapters.fullNode
|
||||
if (!this.fullNode) {
|
||||
throw new Error('Full node adapter required when instantiating Blockchain use cases.')
|
||||
}
|
||||
}
|
||||
|
||||
async getBestBlockHash () {
|
||||
return this.fullNode.call('getbestblockhash')
|
||||
}
|
||||
|
||||
async getBlockchainInfo () {
|
||||
return this.fullNode.call('getblockchaininfo')
|
||||
}
|
||||
|
||||
async getBlockCount () {
|
||||
return this.fullNode.call('getblockcount')
|
||||
}
|
||||
|
||||
async getBlockHeader ({ hash, verbose = false }) {
|
||||
return this.fullNode.call('getblockheader', [hash, verbose])
|
||||
}
|
||||
|
||||
async getBlockHeaders ({ hashes, verbose = false }) {
|
||||
try {
|
||||
const promises = hashes.map(hash =>
|
||||
this.fullNode.call('getblockheader', [hash, verbose], `getblockheader-${hash}`)
|
||||
)
|
||||
|
||||
return await Promise.all(promises)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in BlockchainUseCases.getBlockHeaders()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getChainTips () {
|
||||
return this.fullNode.call('getchaintips')
|
||||
}
|
||||
|
||||
async getDifficulty () {
|
||||
return this.fullNode.call('getdifficulty')
|
||||
}
|
||||
|
||||
async getMempoolEntry ({ txid }) {
|
||||
return this.fullNode.call('getmempoolentry', [txid])
|
||||
}
|
||||
|
||||
async getMempoolEntries ({ txids }) {
|
||||
try {
|
||||
const promises = txids.map(txid =>
|
||||
this.fullNode.call('getmempoolentry', [txid], `getmempoolentry-${txid}`)
|
||||
)
|
||||
|
||||
return await Promise.all(promises)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in BlockchainUseCases.getMempoolEntries()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getMempoolAncestors ({ txid, verbose = false }) {
|
||||
return this.fullNode.call('getmempoolancestors', [txid, verbose])
|
||||
}
|
||||
|
||||
async getMempoolInfo () {
|
||||
return this.fullNode.call('getmempoolinfo')
|
||||
}
|
||||
|
||||
async getRawMempool ({ verbose = false }) {
|
||||
return this.fullNode.call('getrawmempool', [verbose])
|
||||
}
|
||||
|
||||
async getTxOut ({ txid, n, includeMempool }) {
|
||||
return this.fullNode.call('gettxout', [txid, n, includeMempool])
|
||||
}
|
||||
|
||||
async getTxOutProof ({ txid }) {
|
||||
return this.fullNode.call('gettxoutproof', [[txid]])
|
||||
}
|
||||
|
||||
async getTxOutProofs ({ txids }) {
|
||||
try {
|
||||
const promises = txids.map(txid =>
|
||||
this.fullNode.call('gettxoutproof', [[txid]], `gettxoutproof-${txid}`)
|
||||
)
|
||||
|
||||
return await Promise.all(promises)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in BlockchainUseCases.getTxOutProofs()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async verifyTxOutProof ({ proof }) {
|
||||
return this.fullNode.call('verifytxoutproof', [proof])
|
||||
}
|
||||
|
||||
async verifyTxOutProofs ({ proofs }) {
|
||||
try {
|
||||
const promises = proofs.map(proof =>
|
||||
this.fullNode.call('verifytxoutproof', [proof], `verifytxoutproof-${proof.slice(0, 16)}`)
|
||||
)
|
||||
|
||||
return await Promise.all(promises)
|
||||
} catch (err) {
|
||||
wlogger.error('Error in BlockchainUseCases.verifyTxOutProofs()', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getBlock ({ blockhash, verbosity }) {
|
||||
return this.fullNode.call('getblock', [blockhash, verbosity])
|
||||
}
|
||||
|
||||
async getBlockHash ({ height }) {
|
||||
return this.fullNode.call('getblockhash', [height])
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockchainUseCases
|
||||
@@ -8,6 +8,7 @@
|
||||
// import PublishEventUseCase from './publish-event.js'
|
||||
// import QueryEventsUseCase from './query-events.js'
|
||||
// import ManageSubscriptionUseCase from './manage-subscription.js'
|
||||
import BlockchainUseCases from './blockchain/index.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -21,6 +22,7 @@ class UseCases {
|
||||
// this.publishEvent = new PublishEventUseCase({ adapters: this.adapters })
|
||||
// this.queryEvents = new QueryEventsUseCase({ adapters: this.adapters })
|
||||
// this.manageSubscription = new ManageSubscriptionUseCase({ adapters: this.adapters })
|
||||
this.blockchain = new BlockchainUseCases({ adapters: this.adapters })
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
|
||||
Reference in New Issue
Block a user