Browse Source

Merge pull request #90 from Samourai-Wallet/feat_local_indexer

update the remote importer
umbrel
kenshin samourai 5 years ago
committed by GitHub
parent
commit
dab1cbbf9d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 4
      README.md
  3. 1
      docker/my-dojo/.env
  4. 7
      docker/my-dojo/conf/docker-node.conf.tpl
  5. 13
      docker/my-dojo/node/keys.index.js
  6. 34
      keys/index-example.js
  7. 224
      lib/indexer-rpc/rpc-client.js
  8. 179
      lib/remote-importer/btccom-wrapper.js
  9. 4
      lib/remote-importer/esplora-wrapper.js
  10. 90
      lib/remote-importer/insight-wrapper.js
  11. 143
      lib/remote-importer/local-indexer-wrapper.js
  12. 4
      lib/remote-importer/oxt-wrapper.js
  13. 8
      lib/remote-importer/remote-importer.js
  14. 10
      lib/remote-importer/sources-mainnet.js
  15. 10
      lib/remote-importer/sources-testnet.js
  16. 21
      lib/util.js

1
.gitignore

@ -8,4 +8,5 @@ keys/index.js
keys/sslcert/
node_modules/
private-tests/
static/admin/conf/index.js
*.log

4
README.md

@ -65,10 +65,10 @@ Authentication is enforced by an API key and Json Web Tokens.
* Default option relies on the local bitcoind and makes you 100% independent of Samourai Wallet's infrastructure. This option is recommended for better privacy.
* Activation of bitcoind as the data source:
* Edit /keys/index.js and set "explorers.bitcoind" to "active". OXT API will be ignored.
* Edit /keys/index.js and set "indexer.active" to "local_bitcoind". OXT API will be ignored.
* Activation of OXT as the data source (through socks5):
* Edit /keys/index.js and set "explorers.bitcoind" to "inactive".
* Edit /keys/index.js and set "indexer.active" to "third_party_explorer".
* Main drawbacks of using your local bitcoind for these imports:
* This option is considered as experimental.

1
docker/my-dojo/.env

@ -41,7 +41,6 @@ NODE_GAP_EXTERNAL=100
NODE_GAP_INTERNAL=100
NODE_ADDR_FILTER_THRESHOLD=1000
NODE_URL_OXT_API=https://api.oxt.me
NODE_URL_BTCCOM_API=https://tchain.api.btc.com/v3
NODE_URL_ESPLORA_API=https://blockstream.info/testnet
NODE_ADDR_DERIVATION_MIN_CHILD=2
NODE_ADDR_DERIVATION_MAX_CHILD=2

7
docker/my-dojo/conf/docker-node.conf.tpl

@ -20,10 +20,9 @@ NODE_ADMIN_KEY=myAdminKey
# Type: alphanumeric
NODE_JWT_SECRET=myJwtSecret
# Data source used for imports and rescans (bitcoind or OXT)
# Note: support of local bitcoind is an experimental feature
# Values: active | inactive
NODE_IMPORT_FROM_BITCOIND=active
# Indexer or third-party service used for imports and rescans of addresses
# Values: local_bitcoind | third_party_explorer
NODE_ACTIVE_INDEXER=local_bitcoind
# FEE TYPE USED FOR FEES ESTIMATIONS BY BITCOIND
# Allowed values are ECONOMICAL or CONSERVATIVE

13
docker/my-dojo/node/keys.index.js

@ -151,21 +151,18 @@ module.exports = {
transactions: 50
},
/*
* Third party explorers
* Indexer or third party service
* used for fast scan of addresses
*/
explorers: {
// Use local bitcoind for imports and rescans
// or use OXT as a fallback
// Values: active | inactive
bitcoind: process.env.NODE_IMPORT_FROM_BITCOIND,
indexer: {
// Active indexer
// Values: local_bitcoind | third_party_explorer
active: process.env.NODE_ACTIVE_INDEXER,
// Use a SOCKS5 proxy for all communications with external services
// Values: null if no socks5 proxy used, otherwise the url of the socks5 proxy
socks5Proxy: 'socks5h://172.28.1.4:9050',
// OXT (mainnet)
oxt: process.env.NODE_URL_OXT_API,
// BTC.COM (testnet)
btccom: process.env.NODE_URL_BTCCOM_API,
// Esplora (testnet)
esplora: process.env.NODE_URL_ESPLORA_API,
},

34
keys/index-example.js

@ -147,14 +147,23 @@ module.exports = {
transactions: 50
},
/*
* Third party explorers
* Indexer or third party service
* used for fast scan of addresses
*/
explorers: {
// Use local bitcoind for imports and rescans
// or use OXT as a fallback
// Values: active | inactive
bitcoind: 'active',
indexer: {
// Active indexer
// Values: local_bitcoind | local_indexer | third_party_explorer
active: 'local_bitcoind',
// Local indexer
localIndexer: {
// IP address or hostname
host: '127.0.0.1',
// Port
port: 50001,
// Support of batch requests
// Values: active | inactive
batchRequests: 'inactive'
},
// Use a SOCKS5 proxy for all communications with external services
// Values: null if no socks5 proxy used, otherwise the url of the socks5 proxy
socks5Proxy: null,
@ -270,13 +279,14 @@ module.exports = {
multiaddr: {
transactions: 50
},
explorers: {
bitcoind: 'inactive',
indexer: {
active: 'third_party_explorer',
localIndexer: {
host: '127.0.0.1',
port: 50001,
batchRequests: 'inactive'
},
socks5Proxy: null,
insight: [
'https://testnet-api.example.com'
],
btccom: 'https://tchain.api.btc.com/v3',
esplora: 'https://blockstream.info/testnet'
},
addrFilterThreshold: 1000,

224
lib/indexer-rpc/rpc-client.js

@ -0,0 +1,224 @@
/*!
* lib/indexer_rpc/rpc-client.js
* Copyright © 2019 Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const net = require('net')
const makeConcurrent = require('make-concurrent')
const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key]
/**
* RPC client for an indexer
* following the Electrum protocol
*/
class RpcClient {
/**
* Constructor
*/
constructor(opts) {
this._opts = {
host: keys.indexer.localIndexer.host,
port: keys.indexer.localIndexer.port,
concurrency: Infinity,
timeout: 10000
}
if (this._opts.concurrency !== Infinity) {
this._call = makeConcurrent(
this._call.bind(this),
{concurrency: this._opts.concurrency}
)
}
}
/**
* Set an option
* @param {string} key
* @param {*} value
* @return {RpcClient}
*/
set(key, value) {
this._opts[key] = value
return this
}
/**
* Get an option
* @param {string} key
* @return {*}
*/
get(key) {
return this._opts[key]
}
/**
* Check if an error returned by the wrapper
* is a connection error.
* @param {string} err - error message
* @returns {boolean} returns true if message related to a connection error
*/
static isConnectionError(err) {
if (typeof err != 'string')
return false
const isTimeoutError = (err.indexOf('connect ETIMEDOUT') != -1)
const isConnRejected = (err.indexOf('Connection Rejected') != -1)
return (isTimeoutError || isConnRejected)
}
/**
* Check if the rpc api is ready to process requests
* @returns {Promise}
*/
static async waitForIndexerRpcApi(opts) {
let client = new RpcClient(opts)
try {
await client.sendRequest('server.version', 'dojo', ['1.0', '1.4'])
} catch(e) {
client = null
Logger.info('Indexer RPC API is still unreachable. New attempt in 20s.')
return util.delay(20000).then(() => {
return RpcClient.waitForIndexerRpcApi()
})
}
}
/**
* Send multiple requests (batch mode)
* @param {Object[]} batch - array of objects {method: ..., params: ...}
* @return {Promise}
*/
async sendBatch(batch) {
return this._call(batch, true)
}
/**
* Send multiple requests (flood mode)
* @param {Object[]} batch - array of objects {method: ..., params: ...}
* @return {Promise}
*/
async sendRequests(batch) {
return this._call(batch, false)
}
/**
* Send a request
* @param {string} method - called method
* @return {Promise}
*/
async sendRequest(method, ...params) {
const batch = [{method: method, params: params}]
const ret = await this._call(batch, false)
return ret[0]
}
/**
* Send requests (internal method)
* @param {Object[]} data - array of objects {method: ..., params: ...}
* @returns {Promise}
*/
async _call(data, batched) {
return new Promise((resolve, reject) => {
let methodId = 0
let requests = []
let responses = []
let response = ''
const requestErrorMsg = `Indexer JSON-RPC: host=${this._opts.host} port=${this._opts.port}:`
// Prepare an array of requests
requests = data.map(req => {
return JSON.stringify({
jsonrpc: '2.0',
method: req.method,
params: req.params || [],
id: methodId++
})
})
// If batch mode
// send a single batched request
if (batched)
requests = [`[${requests.join(',')}]`]
// Initialize the connection
const conn = net.Socket()
conn.setTimeout(this._opts.timeout)
conn.setEncoding('utf8')
conn.setKeepAlive(true, 0)
conn.setNoDelay(true)
conn.on('connect', () => {
// Send the requests
for (let request of requests)
conn.write(request + '\n')
})
conn.on('timeout', () => {
const e = new Error('ETIMEDOUT')
e.errorno = 'ETIMEDOUT'
e.code = 'ETIMEDOUT'
e.connect = false
conn.emit('error', e)
})
conn.on('data', chunk => {
// Process the received chunk char by char
for (let c of chunk) {
response += c
// Detect the end of a response
if (c == '\n') {
try {
// Parse the response
let parsed = JSON.parse(response)
if (parsed.error)
throw new Error(JSON.stringify(parsed.error))
// Add the parsed reponse to the array of responses
if (batched) {
responses = parsed.map(p => { return {idxAddr: p.id, txs: p.result} })
} else {
responses.push({idxAddr: parsed.id, txs: parsed.result})
}
// Reset the response
response = ''
// If all responses have been received
// close the connection
if (responses.length == data.length)
conn.end()
} catch (err) {
reject(
new Error(`${requestErrorMsg} Error Parsing JSON: ${err.message}, data: ${response}`)
)
}
}
}
})
conn.on('end', e => {
// Connection closed
// we can return the responses
resolve(responses)
})
conn.on('error', e => {
reject(new Error(`${requestErrorMsg} Request error: ${e}`))
})
// Connect to the RPC API
conn.connect({
host: this._opts.host,
port: this._opts.port
})
})
}
}
module.exports = RpcClient

179
lib/remote-importer/btccom-wrapper.js

@ -1,179 +0,0 @@
/*!
* lib/remote-importer\btccom-wrapper.js
* Copyright © 2019 Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const rp = require('request-promise-native')
const addrHelper = require('../bitcoin/addresses-helper')
const util = require('../util')
const Logger = require('../logger')
const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key]
const Wrapper = require('./wrapper')
/**
* Wrapper for the btc.com block explorer APIs
*/
class BtcComWrapper extends Wrapper {
/**
* Constructor
*/
constructor(url) {
super(url, keys.explorers.socks5Proxy)
}
/**
* Send a GET request to the API
* @param {string} route
* @returns {Promise}
*/
async _get(route) {
const params = {
url: `${this.base}${route}`,
method: 'GET',
json: true,
timeout: 15000
}
// Sets socks proxy agent if required
if (keys.explorers.socks5Proxy != null)
params['agent'] = this.socksProxyAgent
return rp(params)
}
/**
* Get a page of transactions related to a given address
* @param {string} address - bitcoin address
* @param {integer} page - page index
* @returns {Promise}
*/
async _getTxsForAddress(address, page) {
const uri = `/address/${address}/tx?page=${page}&verbose=1`
const results = await this._get(uri)
return results.data.list.map(tx => tx.hash)
}
/**
* Retrieve information for a given address
* @param {string} address - bitcoin address
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an object
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddress(address, filterAddr) {
const reqAddr = addrHelper.isBech32(address)
? addrHelper.getScriptHashFromBech32(address)
: address
const uri = `/address/${reqAddr}`
const result = await this._get(uri)
const ret = {
address: address,
ntx: result.data.tx_count,
txids: []
}
// Check if we should filter this address
if (filterAddr && ret.ntx > keys.addrFilterThreshold) {
Logger.info(` import of ${ret.address} rejected (too many transactions - ${ret.ntx})`)
return ret
}
const nbPagesApi = Math.ceil(ret.ntx / BtcComWrapper.NB_TXS_PER_PAGE)
const nbPages = Math.min(20, nbPagesApi)
const aPages = new Array(nbPages)
const listPages = Array.from(aPages, (val, idx) => idx + 1)
const results = await util.seriesCall(listPages, idx => {
return this._getTxsForAddress(reqAddr, idx)
})
for (let txids of results)
ret.txids = ret.txids.concat(txids)
return ret
}
/**
* Retrieve information for a given list of addresses
* @param {string[]} addresses - array of bitcoin addresses
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an array of objects
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddresses(addresses, filterAddr) {
const ret = []
const reqAddresses = []
const xlatedBech32Addr = {}
for (let a of addresses) {
if (addrHelper.isBech32(a)) {
const scriptHash = addrHelper.getScriptHashFromBech32(a)
reqAddresses.push(scriptHash)
xlatedBech32Addr[scriptHash] = a
} else {
reqAddresses.push(a)
}
}
// Send a batch request for all the addresses
const strReqAddresses = reqAddresses.join(',')
const uri = `/address/${strReqAddresses}`
const results = await this._get(uri)
const foundAddresses = Array.isArray(results.data)
? results.data
: [results.data]
for (let a of foundAddresses) {
if (a && a.tx_count > 0) {
// Translate bech32 address
const address = xlatedBech32Addr.hasOwnProperty(a.address)
? xlatedBech32Addr[a.address]
: a.address
if (a.tx_count <= 2) {
// Less than 3 transactions for this address
// all good
const retAddr = {
address: address,
ntx: a.tx_count,
txids: []
}
retAddr.txids = (a.tx_count == 1)
? [a.first_tx]
: [a.first_tx, a.last_tx]
ret.push(retAddr)
} else {
// More than 2 transactions for this address
// We need more requests to the API
if (filterAddr && a.tx_count > keys.addrFilterThreshold) {
Logger.info(` import of ${address} rejected (too many transactions - ${a.tx_count})`)
} else {
const retAddr = await this.getAddress(address)
ret.push(retAddr)
}
}
}
}
return ret
}
}
// BTC.COM acepts a max of 50txs per page
BtcComWrapper.NB_TXS_PER_PAGE = 50
module.exports = BtcComWrapper

4
lib/remote-importer/esplora-wrapper.js

@ -22,7 +22,7 @@ class EsploraWrapper extends Wrapper {
* Constructor
*/
constructor(url) {
super(url, keys.explorers.socks5Proxy)
super(url, keys.indexer.socks5Proxy)
}
/**
@ -39,7 +39,7 @@ class EsploraWrapper extends Wrapper {
}
// Sets socks proxy agent if required
if (keys.explorers.socks5Proxy != null)
if (keys.indexer.socks5Proxy != null)
params['agent'] = this.socksProxyAgent
return rp(params)

90
lib/remote-importer/insight-wrapper.js

@ -1,90 +0,0 @@
/*!
* lib/remote-importer/insight-wrapper.js
* Copyright © 2019 Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const rp = require('request-promise-native')
const Logger = require('../logger')
const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key]
const Wrapper = require('./wrapper')
/**
* Wrapper for the Insight block explorer APIs
*/
class InsightWrapper extends Wrapper {
/**
* Constructor
*/
constructor(url) {
super(url, keys.explorers.socks5Proxy)
}
/**
* Send a GET request to the API
* @param {string} route
* @returns {Promise}
*/
async _get(route) {
const params = {
url: `${this.base}${route}`,
method: 'GET',
json: true,
timeout: 15000
}
// Sets socks proxy agent if required
if (keys.explorers.socks5Proxy != null)
params['agent'] = this.socksProxyAgent
return rp(params)
}
/**
* Retrieve information for a given address
* @param {string} address - bitcoin address
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an object
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddress(address, filterAddr) {
const uri = `/addr/${address}`
// Param filterAddr isn't used for insight
const result = await this._get(uri)
const ret = {
address: result.addrStr,
txids: [],
ntx: result.txApperances
}
// Check if we should filter this address
if (filterAddr && ret.ntx > keys.addrFilterThreshold) {
Logger.info(` import of ${ret.address} rejected (too many transactions - ${ret.ntx})`)
return ret
}
ret.txids = result.transactions
return ret
}
/**
* Retrieve information for a given list of addresses
* @param {string} addresses - array of bitcoin addresses
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an array of objects
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddresses(addresses, filterAddr) {
// Not implemented for this api
throw "Not implemented"
}
}
module.exports = InsightWrapper

143
lib/remote-importer/local-indexer-wrapper.js

@ -0,0 +1,143 @@
/*!
* lib/remote-importer/local-indexer-wrapper.js
* Copyright © 2019 Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const bitcoin = require('bitcoinjs-lib')
const Logger = require('../logger')
const util = require('../util')
const network = require('../bitcoin/network')
const activeNet = network.network
const keys = require('../../keys')[network.key]
const RpcClient = require('../indexer-rpc/rpc-client')
const Wrapper = require('./wrapper')
/**
* Wrapper for a local indexer
* Currently supports indexers
* providing a RPC API compliant
* with a subset of the electrum protocol
*/
class LocalIndexerWrapper extends Wrapper {
/**
* Constructor
*/
constructor() {
super(null, null)
// RPC client
this.client = new RpcClient()
}
/**
* Translate a bitcoin address into a script hash
* (@see https://electrumx.readthedocs.io/en/latest/protocol-basics.html#script-hashes)
* @param {string} address - bitcoin address
* @returns {string} returns the script hash associated to the address
*/
_getScriptHash(address) {
const bScriptPubKey = bitcoin.address.toOutputScript(address, activeNet)
const bScriptHash = bitcoin.crypto.sha256(bScriptPubKey)
return util.reverseBuffer(bScriptHash).toString('hex')
}
/**
* Retrieve information for a given address
* @param {string} address - bitcoin address
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an object
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddress(address, filterAddr) {
const ret = {
address: address,
ntx: 0,
txids: []
}
const scriptHash = this._getScriptHash(address)
const results = await this.client.sendRequest(
LocalIndexerWrapper.GET_HISTORY_RPC_CMD,
scriptHash
)
for (let r of results.txs) {
ret.txids.push(r.tx_hash)
ret.ntx++
}
if (filterAddr && ret.ntx > keys.addrFilterThreshold) {
Logger.info(` import of ${address} rejected (too many transactions - ${ret.ntx})`)
return {
address: address,
ntx: 0,
txids: []
}
}
return ret
}
/**
* Retrieve information for a given list of addresses
* @param {string} addresses - array of bitcoin addresses
* @param {boolean} filterAddr - True if an upper bound should be used
* for #transactions associated to the address, False otherwise
* @returns {Promise} returns an array of objects
* { address: <bitcoin_address>, txids: <txids>, ntx: <total_nb_txs>}
*/
async getAddresses(addresses, filterAddr) {
const ret = {}
// Build an array of script hashes
const scriptHashes = addresses.map(a => this._getScriptHash(a))
// Build an array of commands
const commands = scriptHashes.map(s => {
return {
method: LocalIndexerWrapper.GET_HISTORY_RPC_CMD,
params: [s]
}
})
// Send the requests
const results = (keys.indexer.localIndexer.batchRequests == 'active')
? await this.client.sendBatch(commands)
: await this.client.sendRequests(commands)
for (let r of results) {
const addr = addresses[r.idxAddr]
const txids = r.txs.map(t => t.tx_hash)
ret[addr] = {
address: addr,
ntx: txids.length,
txids: txids
}
}
const aRet = Object.values(ret)
for (let i in aRet) {
if (filterAddr && aRet[i].ntx > keys.addrFilterThreshold) {
Logger.info(` import of ${aRet[i].address} rejected (too many transactions - ${aRet[i].ntx})`)
aRet.splice(i, 1)
}
}
return aRet
}
}
/**
* Get history RPC command (Electrum protocol)
*/
LocalIndexerWrapper.GET_HISTORY_RPC_CMD = 'blockchain.scripthash.get_history'
module.exports = LocalIndexerWrapper

4
lib/remote-importer/oxt-wrapper.js

@ -20,7 +20,7 @@ class OxtWrapper extends Wrapper {
* Constructor
*/
constructor(url) {
super(url, keys.explorers.socks5Proxy)
super(url, keys.indexer.socks5Proxy)
}
/**
@ -37,7 +37,7 @@ class OxtWrapper extends Wrapper {
}
// Sets socks proxy agent if required
if (keys.explorers.socks5Proxy != null)
if (keys.indexer.socks5Proxy != null)
params['agent'] = this.socksProxyAgent
return rp(params)

8
lib/remote-importer/remote-importer.js

@ -144,9 +144,13 @@ class RemoteImporter {
let gaps = [gap.external, gap.internal]
// Allow custom higher gap limits
// for local scans relying on bitcoind
if (gapLimit && keys.explorers.bitcoind)
// for local scans relying on bitcoind or on a local indexer
if (gapLimit
&& ((keys.indexer.active == 'local_bitcoind')
|| (keys.indexer.active == 'local_indexer'))
) {
gaps = [gapLimit, gapLimit]
}
startIndex = (startIndex == null) ? -1 : startIndex - 1

10
lib/remote-importer/sources-mainnet.js

@ -9,6 +9,7 @@ const Logger = require('../logger')
const keys = require('../../keys')[network.key]
const Sources = require('./sources')
const BitcoindWrapper = require('./bitcoind-wrapper')
const LocalIndexerWrapper = require('./local-indexer-wrapper')
const OxtWrapper = require('./oxt-wrapper')
@ -29,14 +30,19 @@ class SourcesMainnet extends Sources {
* Initialize the external data source
*/
_initSource() {
if (keys.explorers.bitcoind == 'active') {
if (keys.indexer.active == 'local_bitcoind') {
// If local bitcoind option is activated
// we'll use the local node as our unique source
this.source = new BitcoindWrapper()
Logger.info('Activated Bitcoind as the data source for imports')
} else if (keys.indexer.active == 'local_indexer') {
// If local indexer option is activated
// we'll use the local indexer as our unique source
this.source = new LocalIndexerWrapper()
Logger.info('Activated local indexer as the data source for imports')
} else {
// Otherwise, we'll use the rest api provided by OXT
this.source = new OxtWrapper(keys.explorers.oxt)
this.source = new OxtWrapper(keys.indexer.oxt)
Logger.info('Activated OXT API as the data source for imports')
}
}

10
lib/remote-importer/sources-testnet.js

@ -10,6 +10,7 @@ const Logger = require('../logger')
const keys = require('../../keys')[network.key]
const Sources = require('./sources')
const BitcoindWrapper = require('./bitcoind-wrapper')
const LocalIndexerWrapper = require('./local-indexer-wrapper')
const EsploraWrapper = require('./esplora-wrapper')
@ -30,14 +31,19 @@ class SourcesTestnet extends Sources {
* Initialize the external data source
*/
_initSource() {
if (keys.explorers.bitcoind == 'active') {
if (keys.indexer.active == 'local_bitcoind') {
// If local bitcoind option is activated
// we'll use the local node as our unique source
this.source = new BitcoindWrapper()
Logger.info('Activated Bitcoind as the data source for imports')
} else if (keys.indexer.active == 'local_indexer') {
// If local indexer option is activated
// we'll use the local indexer as our unique source
this.source = new LocalIndexerWrapper()
Logger.info('Activated local indexer as the data source for imports')
} else {
// Otherwise, we'll use the rest api provided by Esplora
this.source = new EsploraWrapper(keys.explorers.esplora)
this.source = new EsploraWrapper(keys.indexer.esplora)
Logger.info('Activated Esplora API as the data source for imports')
}
}

21
lib/util.js

@ -132,6 +132,27 @@ class Util {
return Util.isHashStr(hash) && hash.length == 64
}
/**
* Reverse buffer content (swap endiannes)
*/
static reverseBuffer(buffer) {
if (buffer.length < 1)
return buffer
let j = buffer.length - 1
let tmp = 0
for (let i = 0; i < buffer.length / 2; i++) {
tmp = buffer[i]
buffer[i] = buffer[j]
buffer[j] = tmp
j--
}
return buffer
}
/**
* Sum an array of values
*/

Loading…
Cancel
Save