fix(Util.sweepWif): Replaced blockbook with electrumx

This commit is contained in:
Chris Troutner
2021-03-01 11:35:07 -08:00
parent 64e59f607a
commit b4d29a5856
4 changed files with 117 additions and 112 deletions
+3 -3
View File
@@ -52,7 +52,7 @@ const electrumxv4 = new ElectrumXV4()
electrumxv4.connect()
const encryptionv4 = new EncryptionV4()
const pricev4 = new PriceV4()
const utilV4 = new UtilV4()
const utilV4 = new UtilV4({ electrumx: electrumxv4 })
const app = express()
@@ -208,11 +208,11 @@ function onError (error) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`)
process.exit(1)
// break
// break
case 'EADDRINUSE':
console.error(`${bind} is already in use`)
process.exit(1)
// break
// break
default:
throw error
}
+28 -30
View File
@@ -7,9 +7,6 @@ const axios = require('axios')
const routeUtils = require('./route-utils')
const wlogger = require('../../util/winston-logging')
const Blockbook = require('./blockbook')
const blockbook = new Blockbook()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
@@ -38,9 +35,22 @@ const bchjs = new BCHJS()
let _this
class UtilRoute {
constructor () {
constructor (utilConfig) {
this.bchjs = bchjs
this.blockbook = blockbook
// this.blockbook = blockbook
if (!utilConfig) {
throw new Error(
'Must pass a config object when instantiating the Util library.'
)
}
if (!utilConfig.electrumx) {
throw new Error(
'Must pass an instance of Electrumx when instantiating the Util library.'
)
}
this.electrumx = utilConfig.electrumx
this.router = router
this.router.get('/', this.root)
@@ -172,7 +182,7 @@ class UtilRoute {
} = routeUtils.setEnvVars()
// Loop through each address and creates an array of requests to call in parallel
const promises = addresses.map(async address => {
const promises = addresses.map(async (address) => {
requestConfig.data.id = 'validateaddress'
requestConfig.data.method = 'validateaddress'
requestConfig.data.params = [address]
@@ -184,7 +194,7 @@ class UtilRoute {
const axiosResult = await axios.all(promises)
// Retrieve the data part of the result.
const result = axiosResult.map(x => x.data.result)
const result = axiosResult.map((x) => x.data.result)
res.status(200)
return res.json(result)
@@ -250,12 +260,11 @@ class UtilRoute {
const fromAddr = bchjs.ECPair.toCashAddress(ecPair)
// Get a balance on the public address
const balances = await _this.blockbook.balanceFromBlockbook(fromAddr)
const balances = await _this.electrumx._balanceFromElectrumx(fromAddr)
// console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
// Total balance is the sum of the confirmed and unconfirmed balance.
const totalBalance =
Number(balances.balance) + Number(balances.unconfirmedBalance)
const totalBalance = balances.confirmed + balances.unconfirmed
// Exit if balance is zero.
if (isNaN(totalBalance) || totalBalance === 0) {
@@ -270,7 +279,7 @@ class UtilRoute {
}
// Get all UTXOs help by the address.
const utxos = await _this.blockbook.utxosFromBlockbook(fromAddr)
const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const tokenUtxos = []
@@ -375,7 +384,7 @@ class UtilRoute {
let utxos = options.utxos
// Ensure all utxos have the satoshis property.
utxos = utxos.map(x => {
utxos = utxos.map((x) => {
x.satoshis = Number(x.value)
return x
})
@@ -393,9 +402,9 @@ class UtilRoute {
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
originalAmount = originalAmount + utxo.satoshis
originalAmount = originalAmount + utxo.value
transactionBuilder.addInput(utxo.txid, utxo.vout)
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
}
if (originalAmount < 546) {
@@ -430,7 +439,7 @@ class UtilRoute {
ecPair,
redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.satoshis
utxo.value
)
}
@@ -468,7 +477,7 @@ class UtilRoute {
// Ensure there is only one class of token in the wallet. Throw an error if
// there is more than one.
const tokenId = tokenUtxos[0].tokenId
const otherTokens = tokenUtxos.filter(x => x.tokenId !== tokenId)
const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId)
if (otherTokens.length > 0) {
throw new Error(
'Multiple token classes detected. This function only supports a single class of token.'
@@ -490,9 +499,9 @@ class UtilRoute {
for (let i = 0; i < allUtxos.length; i++) {
const utxo = allUtxos[i]
originalAmount = originalAmount + utxo.satoshis
originalAmount = originalAmount + utxo.value
transactionBuilder.addInput(utxo.txid, utxo.vout)
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
}
if (originalAmount < 300) {
@@ -572,7 +581,7 @@ class UtilRoute {
ecPair,
redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL,
thisUtxo.satoshis
thisUtxo.value
)
}
@@ -592,15 +601,4 @@ class UtilRoute {
}
}
// module.exports = {
// router,
// // testableComponents: {
// // root,
// // validateAddressSingle,
// // validateAddressBulk,
// // sweepWif
// // }
// UtilRoute
// }
module.exports = UtilRoute
+42 -47
View File
@@ -13,75 +13,70 @@ const mockAddress = {
isscript: false
}
// const mockBalance = {
// page: 1,
// totalPages: 1,
// itemsOnPage: 1000,
// address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k',
// balance: '2546',
// totalReceived: '2546',
// totalSent: '0',
// unconfirmedBalance: '0',
// unconfirmedTxs: 0,
// txs: 2,
// txids: [
// 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
// '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
// ]
// }
const mockBalance = {
page: 1,
totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k',
balance: '2546',
totalReceived: '2546',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 2,
txids: [
'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
'44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
]
confirmed: 2546,
unconfirmed: 0
}
const mockUtxos = [
{
txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
vout: 0,
value: '2000',
height: 605873,
confirmations: 298,
satoshis: 2000
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0,
value: 2000,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
}
]
const mockThreeUtxos = [
{
txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
vout: 0,
value: '2000',
height: 605873,
confirmations: 298,
satoshis: 2000
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0,
value: 2000,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
}
]
const mockIsTokenUtxos = [
false,
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_out: 1,
value: 546,
height: 605873,
confirmations: 298,
satoshis: 546,
+44 -32
View File
@@ -4,15 +4,22 @@
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.
These tests use this private key:
L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq
Which corresponds to this address:
bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
// Public npm libraries
const assert = require('chai').assert
const nock = require('nock') // HTTP mocking
const sinon = require('sinon')
// Local libraries
const Electrumx = require('../../src/routes/v4/electrumx')
const UtilRoute = require('../../src/routes/v4/util')
let originalEnvVars // Used during transition from integration to unit tests.
@@ -25,13 +32,16 @@ const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
// const UtilRoute = utilRoute.UtilRoute
const utilRouteInst = new UtilRoute()
// const electrumx = new Electrumx()
const utilRouteInst = new UtilRoute({ electrumx: {} })
describe('#Util', () => {
let req, res
let sandbox
let electrumx
let utilRoute
before(() => {
before(async () => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
@@ -48,6 +58,9 @@ describe('#Util', () => {
process.env.RPC_USERNAME = 'fakeusername'
process.env.RPC_PASSWORD = 'fakepassword'
}
electrumx = new Electrumx()
// await electrumx.connect()
})
// Setup the mocks before each test.
@@ -65,6 +78,8 @@ describe('#Util', () => {
if (!nock.isActive()) nock.activate()
sandbox = sinon.createSandbox()
utilRoute = new UtilRoute({ electrumx })
})
afterEach(() => {
@@ -358,29 +373,29 @@ describe('#Util', () => {
// Mock the RPC call for unit tests.
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.mockIsTokenUtxos)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt',
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
wif: 'L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq',
toAddr: 'bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid')
@@ -390,13 +405,13 @@ describe('#Util', () => {
// Mock the RPC call for unit tests.
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -404,33 +419,30 @@ describe('#Util', () => {
balanceOnly: true
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isNumber(result)
})
}
// Unit tests only
if (process.env.TEST === 'unit') {
it('should generate transaction for valid BCH-only sweep', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves([false, false])
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -438,7 +450,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid')
@@ -446,22 +458,22 @@ describe('#Util', () => {
it('should throw 422 error if no non-token UTXOs', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.tokensOnly)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -469,7 +481,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422)
@@ -482,22 +494,22 @@ describe('#Util', () => {
it('should detect and throw error for multiple token classes', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockThreeUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.multipleTokens)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -505,7 +517,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422)