Compare commits

...
21 Commits
Author SHA1 Message Date
Chris Troutner e96b9e7a44 Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
fix(v5 Fulcrum): Requiring FULCRUM_API env var when starting server
2021-06-16 18:48:12 -07:00
Chris Troutner d29f99965f fix(v5 Fulcrum): Requiring FULCRUM_API env var when starting server 2021-06-16 18:33:12 -07:00
Chris Troutner 9825ec1bab Merge pull request #147 from Permissionless-Software-Foundation/dh-v5-dsproof-endpoint
feat(dsproof): Created v5 GET dsproof endpoint
2021-06-09 07:38:37 -07:00
Chris Troutner fb61cfeca3 Merge branch 'master' into dh-v5-dsproof-endpoint 2021-06-09 07:08:52 -07:00
Chris Troutner 5f9a635ab5 Merge pull request #146 from Permissionless-Software-Foundation/dh-v5-fulcrum-unconfirmed
feat(unconfirmed): Created v5 Fulcrum /unconfirmed endpoints
2021-06-09 07:08:16 -07:00
Daniel Gonzalez 41c5504846 feat(dsproof): Created v5 GET dsproof endpoint 2021-06-08 20:45:03 -04:00
Daniel Gonzalez e5f9455fbb feat(unconfirmed): Created v5 Fulcrum /unconfirmed endpoints 2021-06-07 23:39:51 -04:00
Chris Troutner 20cb48caff Merge pull request #145 from Permissionless-Software-Foundation/dh-v5-fulcrum-transactions
feat(txs): Created v5 Fulcrum /transactions endpoints in bch-api
2021-06-05 08:35:26 -07:00
Daniel Gonzalez f241aed5b2 feat(txs): Created v5 Fulcrum /transactions endpoints in bch-api 2021-06-04 21:43:45 -04:00
Chris Troutner 87c2faa6af Merge pull request #144 from Permissionless-Software-Foundation/dh-v5-fulcrum-block
feat(block): Created v5 Fulcrum /block endpoints in bch-api
2021-06-04 08:07:05 -07:00
Daniel Gonzalez 10a840f48d feat(block): Created v5 Fulcrum /block endpoints in bch-api 2021-06-02 23:43:25 -04:00
Chris Troutner 61f9531046 Merge pull request #143 from Permissionless-Software-Foundation/dh-fulcrum-tx-endpoint
feat(tx): Created v5 Fulcrum /tx endpoints in bch-api
2021-06-02 07:10:45 -07:00
Daniel Gonzalez 1ab408a757 feat(tx): Created v5 Fulcrum /tx endpoints in bch-api 2021-06-01 22:20:57 -04:00
Chris Troutner 84df2be3b8 Merge pull request #142 from Permissionless-Software-Foundation/dh-v5-utxo
feat(utxo): Created v5 Fulcrum utxo endpoints in bch-api
2021-05-29 06:51:37 -07:00
Daniel Gonzalez 17376d9dba feat(utxo): Created v5 Fulcrum utxo endpoints in bch-api 2021-05-28 21:12:21 -04:00
Chris Troutner 4289cf4f7c Merge pull request #141 from Permissionless-Software-Foundation/dh-v5-fulcrum
feat(V5): Created v5 Fulcrum POST balance endpoint in bch-api
2021-05-23 07:53:47 -07:00
Chris Troutner f04449be72 fix(electrumx): Fixing context issue around 'this' 2021-05-23 07:52:44 -07:00
Daniel Gonzalez a0f9d065f9 linting 2021-05-22 21:33:16 -04:00
Daniel Gonzalez 20f2e20bcb feat(V5): Created v5 Fulcrum POST balance endpoint in bch-api 2021-05-22 20:55:24 -04:00
Chris Troutner b410b501a1 Merge pull request #140 from Permissionless-Software-Foundation/ct-unstable
fix(startup): Fixing bug in startup
2021-05-19 12:14:22 -07:00
Chris Troutner 31d430e30e fix(startup): Fixing bug in startup 2021-05-19 12:13:11 -07:00
8 changed files with 2407 additions and 767 deletions
+2 -2
View File
@@ -24,8 +24,8 @@
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs",
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha -g '#getBalance' --exit --timeout 30000 test/v5/",
"test:temp2": "export NETWORK=mainnet && mocha -g '#getBalance' --exit --timeout 30000 test/v5/"
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/",
"test:temp2": "export NETWORK=mainnet && mocha -g '#utxosBulk' --exit --timeout 30000 test/v5/"
},
"engines": {
"node": ">=10.15.1"
+3 -2
View File
@@ -46,6 +46,7 @@ const ControlV5 = require('./routes/v5/full-node/control')
const MiningV5 = require('./routes/v5/full-node/mining')
const networkV5 = require('./routes/v5/full-node/network')
const RawtransactionsV5 = require('./routes/v5/full-node/rawtransactions')
const DSProofV5 = require('./routes/v5/full-node/dsproof')
const UtilV5 = require('./routes/v5/util')
const SlpV5 = require('./routes/v5/slp')
const xpubV5 = require('./routes/v5/xpub')
@@ -75,11 +76,10 @@ const miningV5 = new MiningV5()
const rawtransactionsV5 = new RawtransactionsV5()
const slpV5 = new SlpV5()
const electrumxv5 = new ElectrumXV5()
electrumxv5.connect()
const encryptionv5 = new EncryptionV5()
const pricev5 = new PriceV5()
const utilV5 = new UtilV5({ electrumx: electrumxv5 })
const dsproofV5 = new DSProofV5()
const app = express()
app.locals.env = process.env
@@ -188,6 +188,7 @@ app.use(`/${v5prefix}/` + 'electrumx', electrumxv5.router)
app.use(`/${v5prefix}/` + 'encryption', encryptionv5.router)
app.use(`/${v5prefix}/` + 'price', pricev5.router)
app.use(`/${v5prefix}/` + 'util', utilV5.router)
app.use(`/${v5prefix}/` + 'dsproof', dsproofV5.router)
// const ninsight = new Ninsight()
app.use(`/${v5prefix}/` + 'ninsight', ninsight.router)
+632 -761
View File
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
'use strict'
const express = require('express')
const router = express.Router()
const axios = require('axios')
const wlogger = require('../../../util/winston-logging')
const RouteUtils = require('../../../util/route-utils')
const routeUtils = new RouteUtils()
// Used to convert error messages to strings, to safely pass to users.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
let _this
class DSProof {
constructor () {
_this = this
_this.axios = axios
_this.routeUtils = routeUtils
_this.router = router
_this.router.get('/', this.root)
_this.router.get('/getdsproof/:txid', _this.getDSProof)
}
// DRY error handler.
errorHandler (err, res) {
// Attempt to decode the error message.
const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
res.status(500)
return res.json({ error: util.inspect(err) })
}
root (req, res, next) {
return res.json({ status: 'dsproof' })
}
/**
* @api {get} /dsproof/getdsproof/:txid Get Double-Spend Proof.
* @apiName DS Proof.
* @apiGroup DSProof
* @apiDescription Get information for a double-spend proof.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v5/dsproof/getdsproof/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json"
*
*
*/
async getDSProof (req, res, next) {
try {
const txid = req.params.txid
let verbose = 2 // default
if (req.query.verbose === 'true') verbose = 3
if (!txid || txid === '') {
res.status(400)
return res.json({
success: false,
error: 'txid can not be empty'
})
}
if (txid.length !== 64) {
res.status(400)
return res.json({
success: false,
error: `txid must be of length 64 (not ${txid.length})`
})
}
const options = _this.routeUtils.getAxiosOptions()
options.data.id = 'getdsproof'
options.data.method = 'getdsproof'
options.data.params = [txid, verbose]
const response = await _this.axios.request(options)
return res.json(response.data.result)
} catch (err) {
wlogger.error('Error in dsproof.js/getDSProof().', err)
return _this.errorHandler(err, res)
}
}
}
module.exports = DSProof
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
/*
TESTS FOR THE DSPROOF.JS LIBRARY
This test file uses the environment variable TEST to switch between unit
and integration tests. By default, TEST is set to 'unit'. Set this variable
to 'integration' to run the tests against BCH mainnet.
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
const DSProofRoute = require('../../src/routes/v5/full-node/dsproof')
const uut = new DSProofRoute()
// const nock = require('nock') // HTTP mocking
const sinon = require('sinon')
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks')
const mockData = require('./mocks/dsproof-mocks')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
describe('#DSProof', () => {
let req, res
let sandbox
before(() => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
RPC_BASEURL: process.env.RPC_BASEURL,
RPC_USERNAME: process.env.RPC_USERNAME,
RPC_PASSWORD: process.env.RPC_PASSWORD
}
// Set default environment variables for unit tests.
if (!process.env.TEST) process.env.TEST = 'unit'
if (process.env.TEST === 'unit') {
process.env.BITCOINCOM_BASEURL = 'http://fakeurl/api/'
process.env.RPC_BASEURL = 'http://fakeurl/api'
process.env.RPC_USERNAME = 'fakeusername'
process.env.RPC_PASSWORD = 'fakepassword'
}
})
// Setup the mocks before each test.
beforeEach(() => {
// Mock the req and res objects used by Express routes.
req = mockReq
res = mockRes
// Explicitly reset the parmas and body.
req.params = {}
req.body = {}
req.query = {}
sandbox = sinon.createSandbox()
})
afterEach(() => {
// Restore Sandbox
sandbox.restore()
})
after(() => {
// Restore any pre-existing environment variables.
process.env.BITCOINCOM_BASEURL = originalEnvVars.BITCOINCOM_BASEURL
process.env.RPC_BASEURL = originalEnvVars.RPC_BASEURL
process.env.RPC_USERNAME = originalEnvVars.RPC_USERNAME
process.env.RPC_PASSWORD = originalEnvVars.RPC_PASSWORD
})
describe('#root', async () => {
// root route handler.
it('should respond to GET for base route', async () => {
const result = uut.root(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(result.status, 'dsproof', 'Returns static string')
})
})
describe('#getDSProof', async () => {
it('should throw 400 error if txid is missing', async () => {
const result = await uut.getDSProof(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.include(result.error, 'txid can not be empty')
})
it('should throw 400 error if txid is invalid', async () => {
req.params.txid = 'abc123'
const result = await uut.getDSProof(req, res)
assert.hasAllKeys(result, ['error', 'success'])
assert.isFalse(result.success)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.include(result.error, 'txid must be of length 64')
})
it('should throw 503 when network issues', async () => {
req.params.txid =
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
// 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/'
await uut.getDSProof(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 500 or greater expected.'
)
// assert.include(result.error,"Network error: Could not communicate with full node","Error message expected")
})
it('returns proper error when downstream service stalls', async () => {
req.params.txid =
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getDSProof(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 () => {
req.params.txid =
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getDSProof(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 GET double-spend proof', async function () {
req.params.txid =
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: { result: mockData.mockGetDsProof } })
} else {
return this.skip()
}
const result = await uut.getDSProof(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.property(result, 'dspid')
assert.property(result, 'txid')
assert.property(result, 'outpoint')
assert.property(result, 'path')
assert.property(result, 'descendants')
assert.property(result.outpoint, 'txid')
assert.property(result.outpoint, 'vout')
})
})
})
+22
View File
@@ -0,0 +1,22 @@
/*
This library contains mocking data for running unit tests on the dsproof route.
*/
'use strict'
const mockGetDsProof = {
dspid: '6234fff2a9dbcaa50e2c50d74e1d725a2bed1757d7a05a2db7a82df659554397',
txid: 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e',
outpoint: {
txid: '17ebef34f7e9a0692317dd6fbb43ffccff45b7d688a2a754dbccfc5e6d93ce3e',
vout: 1
},
path: ['ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'],
descendants: [
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
]
}
module.exports = {
mockGetDsProof
}
+91 -2
View File
@@ -19,6 +19,43 @@ const utxos = [
}
]
const utxosArray = {
success: true,
utxos: [
{
utxos: [
{
height: 601861,
tx_hash:
'6181c669614fa18039a19b23eb06806bfece1f7514ab457c3bb82a40fe171a6d',
tx_pos: 0,
value: 1000
}
],
address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
},
{
utxos: [
{
height: 604392,
tx_hash:
'7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41',
tx_pos: 0,
value: 1000
},
{
height: 630834,
tx_hash:
'4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53',
tx_pos: 0,
value: 6000
}
],
address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
}
]
}
const balance = {
success: true,
balance: {
@@ -26,7 +63,19 @@ const balance = {
unconfirmed: 0
}
}
const balances = {
success: true,
balances: [
{
balance: { confirmed: 7000, unconfirmed: 0 },
address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
},
{
balance: { confirmed: 7000, unconfirmed: 0 },
address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
}
]
}
const txHistory = [
{
height: 601861,
@@ -117,16 +166,56 @@ const txDetails = {
]
}
const txDetailsBulk = {
success: true,
transactions: [{ details: txDetails }]
}
const blockHeaders = [
'010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6',
'01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c'
]
const blockHeadersBulk = {
success: true,
headers: [{ headers: blockHeaders }, { headers: blockHeaders }]
}
const transactions = [
{
height: 603416,
tx_hash: 'eef683d236d88e978bd406419f144057af3fe1b62ef59162941c1a9f05ded62c'
},
{
height: 646894,
tx_hash: '4c695fae636f3e8e2edc571d11756b880ccaae744390f3950d798ce7b5e25754'
}
]
const transactionsBulk = {
success: true,
transactions: [
{
transactions,
address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
},
{
transactions,
address: 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
}
]
}
module.exports = {
utxos,
utxosArray,
balance,
txHistory,
mempool,
txDetails,
blockHeaders
txDetailsBulk,
blockHeaders,
blockHeadersBulk,
balances,
transactions,
transactionsBulk
}