mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-21 16:52:04 -07:00
Merge pull request #133 from christroutner/unstable
Adding getTxOut as a POST call
This commit is contained in:
@@ -48,6 +48,7 @@ class Blockchain {
|
||||
this.router.get('/getMempoolInfo', this.getMempoolInfo)
|
||||
this.router.get('/getRawMempool', this.getRawMempool)
|
||||
this.router.get('/getTxOut/:txid/:n', this.getTxOut)
|
||||
this.router.post('/getTxOut', this.getTxOutPost)
|
||||
this.router.get('/getTxOutProof/:txid', this.getTxOutProofSingle)
|
||||
this.router.post('/getTxOutProof', this.getTxOutProofBulk)
|
||||
this.router.get('/verifyTxOutProof/:proof', this.verifyTxOutProofSingle)
|
||||
@@ -248,7 +249,7 @@ class Blockchain {
|
||||
* returns an Object with information about blockheader hash.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://rest.bitcoin.com/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
|
||||
* curl -X POST "https://api.fullstack.cash/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
|
||||
*
|
||||
* @apiParam {String} hash block hash
|
||||
* @apiParam {Boolean} verbose Return verbose data
|
||||
@@ -283,7 +284,7 @@ class Blockchain {
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, hashes)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
res.status(429) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
@@ -308,7 +309,7 @@ class Blockchain {
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each hash and creates an array of requests to call in parallel
|
||||
const promises = hashes.map(async hash => {
|
||||
const promises = hashes.map(async (hash) => {
|
||||
options.data.id = 'getblockheader'
|
||||
options.data.method = 'getblockheader'
|
||||
options.data.params = [hash, verbose]
|
||||
@@ -319,7 +320,7 @@ class Blockchain {
|
||||
const axiosResult = await _this.axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
const result = axiosResult.map((x) => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
@@ -456,7 +457,7 @@ class Blockchain {
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
res.status(429) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
@@ -481,7 +482,7 @@ class Blockchain {
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
const promises = txids.map(async (txid) => {
|
||||
options.data.id = 'getmempoolentry'
|
||||
options.data.method = 'getmempoolentry'
|
||||
options.data.params = [txid]
|
||||
@@ -492,7 +493,7 @@ class Blockchain {
|
||||
const axiosResult = await _this.axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
const result = axiosResult.map((x) => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
@@ -684,6 +685,60 @@ class Blockchain {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /blockchain/getTxOut Validate a UTXO
|
||||
* @apiName getTxOut
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription Returns details about an unspent transaction output (UTXO).
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X POST "https://api.fullstack.cash/v3/blockchain/getTxOut" -H "Content-Type: application/json" -d '{"txid":"a402836c7ced7d7ad2df26d7ee5235f2605d59d65c5c567a750f9eaf186ebb47","vout": 0, "mempool": true}'
|
||||
*
|
||||
* @apiParam {String} txid Transaction id (required)
|
||||
* @apiParam {Number} vout of transaction (required)
|
||||
* @apiParam {Boolean} mempool Check mempool or not (optional)
|
||||
*
|
||||
*/
|
||||
// Returns details about an unspent transaction output.
|
||||
async getTxOutPost (req, res, next) {
|
||||
try {
|
||||
const txid = req.body.txid
|
||||
let n = req.body.vout
|
||||
const mempool = req.body.mempool ? req.body.mempool : true
|
||||
|
||||
// Validate input parameter
|
||||
if (!txid || txid === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'txid can not be empty' })
|
||||
}
|
||||
|
||||
if (n === undefined || n === '') {
|
||||
res.status(400)
|
||||
return res.json({ error: 'vout can not be empty' })
|
||||
}
|
||||
n = parseInt(n)
|
||||
|
||||
// Axios options
|
||||
const options = _this.routeUtils.getAxiosOptions()
|
||||
|
||||
options.data.id = 'gettxout'
|
||||
options.data.method = 'gettxout'
|
||||
options.data.params = [txid, n, mempool]
|
||||
|
||||
// console.log(`requestConfig: ${JSON.stringify(requestConfig, null, 2)}`)
|
||||
|
||||
const response = await _this.axios.request(options)
|
||||
|
||||
return res.json(response.data.result)
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
// logger.error(`Error in rawtransactions/decodeRawTransaction: `, err)
|
||||
wlogger.error('Error in blockchain.ts/getTxOutPost().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /blockchain/getTxOutProofSingle/:txid Get Tx Out Proof
|
||||
* @apiName getTxOutProofSingle
|
||||
@@ -739,7 +794,7 @@ class Blockchain {
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, txids)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
res.status(429) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
@@ -766,7 +821,7 @@ class Blockchain {
|
||||
)
|
||||
|
||||
// Loop through each txid and creates an array of requests to call in parallel
|
||||
const promises = txids.map(async txid => {
|
||||
const promises = txids.map(async (txid) => {
|
||||
options.data.id = 'gettxoutproof'
|
||||
options.data.method = 'gettxoutproof'
|
||||
options.data.params = [[txid]]
|
||||
@@ -778,7 +833,7 @@ class Blockchain {
|
||||
const axiosResult = await _this.axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result)
|
||||
const result = axiosResult.map((x) => x.data.result)
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
@@ -833,7 +888,7 @@ class Blockchain {
|
||||
|
||||
// Enforce array size rate limits
|
||||
if (!routeUtils.validateArraySize(req, proofs)) {
|
||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||
res.status(429) // https://github.com/Bitcoin-com/api.fullstack.cash/issues/330
|
||||
return res.json({
|
||||
error: 'Array too large.'
|
||||
})
|
||||
@@ -858,7 +913,7 @@ class Blockchain {
|
||||
)
|
||||
|
||||
// Loop through each proof and creates an array of requests to call in parallel
|
||||
const promises = proofs.map(async proof => {
|
||||
const promises = proofs.map(async (proof) => {
|
||||
options.data.id = 'verifytxoutproof'
|
||||
options.data.method = 'verifytxoutproof'
|
||||
options.data.params = [proof]
|
||||
@@ -870,7 +925,7 @@ class Blockchain {
|
||||
const axiosResult = await _this.axios.all(promises)
|
||||
|
||||
// Extract the data component from the axios response.
|
||||
const result = axiosResult.map(x => x.data.result[0])
|
||||
const result = axiosResult.map((x) => x.data.result[0])
|
||||
|
||||
res.status(200)
|
||||
return res.json(result)
|
||||
|
||||
@@ -1164,6 +1164,7 @@ describe('#BlockchainRouter', () => {
|
||||
assert.isArray(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTxOut()', () => {
|
||||
it('should throw 400 if txid is empty', async () => {
|
||||
const result = await uut.getTxOut(req, res)
|
||||
@@ -1226,6 +1227,7 @@ describe('#BlockchainRouter', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
@@ -1245,6 +1247,7 @@ describe('#BlockchainRouter', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
// This test can only run for unit tests. See TODO at the top of this file.
|
||||
it('should GET /getTxOut', async () => {
|
||||
// Mock the RPC call for unit tests.
|
||||
@@ -1279,6 +1282,124 @@ describe('#BlockchainRouter', () => {
|
||||
assert.isArray(result.scriptPubKey.addresses)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTxOutPost()', () => {
|
||||
it('should throw 400 if txid is empty', async () => {
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'txid can not be empty')
|
||||
})
|
||||
|
||||
it('should throw 400 if n is empty', async () => {
|
||||
req.body.txid = 'sometxid'
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'vout can not be empty')
|
||||
})
|
||||
|
||||
it('should throw 503 when network issues', async () => {
|
||||
// Save the existing RPC URL.
|
||||
const savedUrl2 = process.env.RPC_BASEURL
|
||||
|
||||
// Manipulate the URL to cause a 500 network error.
|
||||
process.env.RPC_BASEURL = 'http://fakeurl/api/'
|
||||
|
||||
req.body.txid =
|
||||
'd65881582ff2bff36747d7a0d0e273f10281abc8bd5c15df5d72f8f3fa779cde'
|
||||
req.body.vout = 0
|
||||
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
// Restore the saved URL.
|
||||
process.env.RPC_BASEURL = savedUrl2
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
assert.include(
|
||||
result.error,
|
||||
'Could not communicate with full node',
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service stalls', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||
|
||||
req.body.txid =
|
||||
'197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d'
|
||||
req.body.vout = 0
|
||||
req.body.mempool = true
|
||||
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
assert.include(
|
||||
result.error,
|
||||
'Could not communicate with full node',
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
|
||||
req.body.txid =
|
||||
'197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d'
|
||||
req.body.vout = 0
|
||||
req.body.mempool = true
|
||||
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
assert.include(
|
||||
result.error,
|
||||
'Could not communicate with full node',
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('should POST /getTxOut', async () => {
|
||||
// Mock the RPC call for unit tests.
|
||||
if (process.env.TEST === 'unit') {
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.resolves({ data: { result: mockData.mockTxOut } })
|
||||
}
|
||||
|
||||
req.body.txid =
|
||||
'197dcda59864b1eee05498fd3c52cad787ec56ab7e635503cb39f9ab6f295d5d'
|
||||
req.body.vout = 0
|
||||
req.body.mempool = true
|
||||
|
||||
const result = await uut.getTxOutPost(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'bestblock',
|
||||
'confirmations',
|
||||
'value',
|
||||
'scriptPubKey',
|
||||
'coinbase'
|
||||
])
|
||||
assert.hasAllKeys(result.scriptPubKey, [
|
||||
'asm',
|
||||
'hex',
|
||||
'reqSigs',
|
||||
'type',
|
||||
'addresses'
|
||||
])
|
||||
assert.isArray(result.scriptPubKey.addresses)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTxOutProofSingle()', () => {
|
||||
it('should throw 400 if txid is empty', async () => {
|
||||
const result = await uut.getTxOutProofSingle(req, res)
|
||||
@@ -1311,6 +1432,7 @@ describe('#BlockchainRouter', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service stalls', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||
@@ -1330,6 +1452,7 @@ describe('#BlockchainRouter', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
@@ -1367,6 +1490,7 @@ describe('#BlockchainRouter', () => {
|
||||
assert.isString(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxOutProofBulk', () => {
|
||||
it('should throw an error for an empty body', async () => {
|
||||
req.body = {}
|
||||
@@ -1407,6 +1531,7 @@ describe('#BlockchainRouter', () => {
|
||||
assert.hasAllKeys(result, ['error'])
|
||||
assert.include(result.error, 'Array too large')
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service stalls', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||
@@ -1425,6 +1550,7 @@ describe('#BlockchainRouter', () => {
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
|
||||
Reference in New Issue
Block a user