Browse Source

Merge branch 'feat_dojo_optimizations'

umbrel
kenshin-samourai 4 years ago
parent
commit
ce22eec4c6
  1. 84
      lib/bitcoin/addresses-helper.js
  2. 3
      lib/remote-importer/bitcoind-wrapper.js
  3. 27
      lib/util.js
  4. 9
      pushtx/pushtx-processor.js
  5. 10
      tracker/block.js
  6. 6
      tracker/mempool-processor.js
  7. 25
      tracker/transaction.js
  8. 17
      tracker/transactions-bundle.js

84
lib/bitcoin/addresses-helper.js

@ -7,7 +7,9 @@
const bitcoin = require('bitcoinjs-lib') const bitcoin = require('bitcoinjs-lib')
const btcMessage = require('bitcoinjs-message') const btcMessage = require('bitcoinjs-message')
const activeNet = require('./network').network const activeNet = require('./network').network
const { p2pkh, p2sh, p2wpkh } = bitcoin.payments const { p2pkh, p2sh, p2wpkh, p2wsh } = bitcoin.payments
const { OPS } = bitcoin.script
/** /**
* A singleton providing Addresses helper functions * A singleton providing Addresses helper functions
@ -106,6 +108,86 @@ class AddressesHelper {
} }
} }
/**
* Check if an output script is a P2PKH script
* @param {Buffer} scriptpubkey - scriptpubkey
* @returns {boolean} return true if output is a P2PKH script, otherwise return false
*/
isP2pkhScript(scriptpubkey) {
return scriptpubkey.length == 25
&& scriptpubkey[0] == OPS.OP_DUP
&& scriptpubkey[1] == OPS.OP_HASH160
&& scriptpubkey[2] == 0x14
&& scriptpubkey[23] == OPS.OP_EQUALVERIFY
&& scriptpubkey[24] == OPS.OP_CHECKSIG
}
/**
* Check if an output script is a P2SH script
* @param {Buffer} scriptpubkey - scriptpubkey
* @returns {boolean} return true if output is a P2SH script, otherwise return false
*/
isP2shScript(scriptpubkey) {
return scriptpubkey.length == 23
&& scriptpubkey[0] == OPS.OP_HASH160
&& scriptpubkey[1] == 0x14
&& scriptpubkey[22] == OPS.OP_EQUAL
}
/**
* Check if an output script is a P2WPKH script
* @param {Buffer} scriptpubkey - scriptpubkey
* @returns {boolean} return true if output is a P2WPKH script, otherwise return false
*/
isP2wpkhScript(scriptpubkey) {
return scriptpubkey.length == 22
&& scriptpubkey[0] == OPS.OP_0
&& scriptpubkey[1] == 0x14
}
/**
* Check if an output script is a P2WSH script
* @param {Buffer} scriptpubkey - scriptpubkey
* @returns {boolean} return true if output is a P2WSH script, otherwise return false
*/
isP2wshScript(scriptpubkey) {
return scriptpubkey.length == 34
&& scriptpubkey[0] == OPS.OP_0
&& scriptpubkey[1] == 0x20
}
/**
* Return the bitcoin address corresponding to an output script
* @param {Buffer} scriptpubkey - scriptpubkey
* @returns {string} bitcoin address
*/
outputScript2Address(scriptpubkey) {
if (this.isP2pkhScript(scriptpubkey))
return p2pkh({
output: scriptpubkey,
network: activeNet,
}).address
if (this.isP2shScript(scriptpubkey))
return p2sh({
output: scriptpubkey,
network: activeNet,
}).address
if (this.isP2wpkhScript(scriptpubkey))
return p2wpkh({
output: scriptpubkey,
network: activeNet,
}).address
if (this.isP2wshScript(scriptpubkey))
return p2wsh({
output: scriptpubkey,
network: activeNet,
}).address
throw 'unknown address format'
}
} }
module.exports = new AddressesHelper() module.exports = new AddressesHelper()

3
lib/remote-importer/bitcoind-wrapper.js

@ -8,6 +8,7 @@ const bitcoin = require('bitcoinjs-lib')
const RpcClient = require('../bitcoind-rpc/rpc-client') const RpcClient = require('../bitcoind-rpc/rpc-client')
const rpcLatestBlock = require('../bitcoind-rpc/latest-block') const rpcLatestBlock = require('../bitcoind-rpc/latest-block')
const Logger = require('../logger') const Logger = require('../logger')
const addrHelper = require('../bitcoin/addresses-helper')
const network = require('../bitcoin/network') const network = require('../bitcoin/network')
const activeNet = network.network const activeNet = network.network
const keys = require('../../keys')[network.key] const keys = require('../../keys')[network.key]
@ -44,7 +45,7 @@ class BitcoindWrapper extends Wrapper {
*/ */
_xlatScriptPubKey(scriptPubKey) { _xlatScriptPubKey(scriptPubKey) {
const bScriptPubKey = Buffer.from(scriptPubKey, 'hex') const bScriptPubKey = Buffer.from(scriptPubKey, 'hex')
return bitcoin.address.fromOutputScript(bScriptPubKey, activeNet) return addrHelper.outputScript2Address(bScriptPubKey)
} }
/** /**

27
lib/util.js

@ -32,10 +32,10 @@ class Util {
* *
* @param {object} parents - map of {[key]: [incoming edge keys]} * @param {object} parents - map of {[key]: [incoming edge keys]}
* @param {object} children - a map of {[key]: [outgoing edge keys]} * @param {object} children - a map of {[key]: [outgoing edge keys]}
* @returns {object} * @returns {object}
* if graph has edges then * if graph has edges then
* return error (graph has at least one cycle) * return error (graph has at least one cycle)
* else * else
* return L (a topologically sorted order) * return L (a topologically sorted order)
*/ */
static topologicalOrdering(parents, children) { static topologicalOrdering(parents, children) {
@ -81,13 +81,22 @@ class Util {
}).then(result => { }).then(result => {
results.push(result) results.push(result)
}) })
}, },
Promise.resolve() Promise.resolve()
).then(function() { ).then(function() {
return results return results
}) })
} }
/**
* Execute parallel asynchronous calls to a function
* over a list of objects
*/
static parallelCall(list, fn) {
const operations = list.map(item => { return fn(item) })
return Promise.all(operations)
}
/** /**
* Delay the call to a function * Delay the call to a function
*/ */
@ -159,16 +168,16 @@ class Util {
/** /**
* Median of an array of values * Median of an array of values
*/ */
static median(arr, sorted) { static median(arr, sorted) {
if (arr.length == 0) return NaN if (arr.length == 0) return NaN
if (arr.length == 1) return arr[0] if (arr.length == 1) return arr[0]
if (!sorted) if (!sorted)
arr.sort(Util.cmpAsc) arr.sort(Util.cmpAsc)
const midpoint = Math.floor(arr.length / 2) const midpoint = Math.floor(arr.length / 2)
if (arr.length % 2) { if (arr.length % 2) {
// Odd-length array // Odd-length array
return arr[midpoint] return arr[midpoint]
@ -252,10 +261,10 @@ class Util {
// "Floor-x" // "Floor-x"
const fx = Math.floor(x) - 1 const fx = Math.floor(x) - 1
// "Mod-x" // "Mod-x"
const mx = x % 1 const mx = x % 1
if (fx + 1 >= N) { if (fx + 1 >= N) {
return arr[fx] return arr[fx]
} else { } else {
@ -340,7 +349,7 @@ class Util {
const parts = [Util.pad10(h), Util.pad10(m), Util.pad10(s)] const parts = [Util.pad10(h), Util.pad10(m), Util.pad10(s)]
if (d > 0) if (d > 0)
parts.splice(0, 0, Util.pad100(d)) parts.splice(0, 0, Util.pad100(d))
const str = parts.join(':') const str = parts.join(':')

9
pushtx/pushtx-processor.js

@ -10,6 +10,7 @@ const Logger = require('../lib/logger')
const errors = require('../lib/errors') const errors = require('../lib/errors')
const db = require('../lib/db/mysql-db-wrapper') const db = require('../lib/db/mysql-db-wrapper')
const RpcClient = require('../lib/bitcoind-rpc/rpc-client') const RpcClient = require('../lib/bitcoind-rpc/rpc-client')
const addrHelper = require('../lib/bitcoin/addresses-helper')
const network = require('../lib/bitcoin/network') const network = require('../lib/bitcoin/network')
const activeNet = network.network const activeNet = network.network
const keys = require('../keys')[network.key] const keys = require('../keys')[network.key]
@ -24,7 +25,7 @@ if (network.key == 'bitcoin') {
/** /**
* A singleton providing a wrapper * A singleton providing a wrapper
* for pushing transactions with the local bitcoind * for pushing transactions with the local bitcoind
*/ */
class PushTxProcessor { class PushTxProcessor {
@ -52,7 +53,7 @@ class PushTxProcessor {
* Enforce a strict verification mode on a list of outputs * Enforce a strict verification mode on a list of outputs
* @param {string} rawtx - raw bitcoin transaction in hex format * @param {string} rawtx - raw bitcoin transaction in hex format
* @param {array} vouts - output indices (integer) * @param {array} vouts - output indices (integer)
* @returns {array} returns the indices of the faulty outputs * @returns {array} returns the indices of the faulty outputs
*/ */
async enforceStrictModeVouts(rawtx, vouts) { async enforceStrictModeVouts(rawtx, vouts) {
const faultyOutputs = [] const faultyOutputs = []
@ -63,12 +64,12 @@ class PushTxProcessor {
} catch(e) { } catch(e) {
throw errors.tx.PARSE throw errors.tx.PARSE
} }
// Check in db if addresses are known and have been used // Check in db if addresses are known and have been used
for (let vout of vouts) { for (let vout of vouts) {
if (vout >= tx.outs.length) if (vout >= tx.outs.length)
throw errors.txout.VOUT throw errors.txout.VOUT
const output = tx.outs[vout] const output = tx.outs[vout]
const address = bitcoin.address.fromOutputScript(output.script, activeNet) const address = addrHelper.outputScript2Address(output.script)
const nbTxs = await db.getAddressNbTransactions(address) const nbTxs = await db.getAddressNbTransactions(address)
if (nbTxs == null || nbTxs > 0) if (nbTxs == null || nbTxs > 0)
faultyOutputs.push(vout) faultyOutputs.push(vout)

10
tracker/block.js

@ -37,7 +37,7 @@ class Block extends TransactionsBundle {
let block let block
const txsForBroadcast = [] const txsForBroadcast = []
try { try {
block = bitcoin.Block.fromHex(this.hex) block = bitcoin.Block.fromHex(this.hex)
this.transactions = block.transactions this.transactions = block.transactions
@ -46,10 +46,10 @@ class Block extends TransactionsBundle {
Logger.error(null, this.header) Logger.error(null, this.header)
return Promise.reject(e) return Promise.reject(e)
} }
const t0 = Date.now() const t0 = Date.now()
let ntx = 0 let ntx = 0
// Filter transactions // Filter transactions
const filteredTxs = await this.prefilterTransactions() const filteredTxs = await this.prefilterTransactions()
@ -78,9 +78,9 @@ class Block extends TransactionsBundle {
// Confirms the transactions // Confirms the transactions
const txids = this.transactions.map(t => t.getId()) const txids = this.transactions.map(t => t.getId())
ntx = txids.length ntx = txids.length
const txidLists = util.splitList(txids, 100) const txidLists = util.splitList(txids, 100)
await util.seriesCall(txidLists, list => db.confirmTransactions(list, blockId)) await util.parallelCall(txidLists, list => db.confirmTransactions(list, blockId))
// Logs and result returned // Logs and result returned
const dt = ((Date.now()-t0)/1000).toFixed(1) const dt = ((Date.now()-t0)/1000).toFixed(1)

6
tracker/mempool-processor.js

@ -69,7 +69,7 @@ class MempoolProcessor extends AbstractProcessor {
/** /**
* Stop processing * Stop processing
*/ */
async stop() { async stop() {
clearInterval(this.checkUnconfirmedId) clearInterval(this.checkUnconfirmedId)
clearInterval(this.processMempoolId) clearInterval(this.processMempoolId)
@ -218,11 +218,11 @@ class MempoolProcessor extends AbstractProcessor {
const unconfirmedTxs = await db.getUnconfirmedTransactions() const unconfirmedTxs = await db.getUnconfirmedTransactions()
if (unconfirmedTxs.length > 0) { if (unconfirmedTxs.length > 0) {
await util.seriesCall(unconfirmedTxs, tx => { await util.parallelCall(unconfirmedTxs, tx => {
try { try {
return this.client.getrawtransaction(tx.txnTxid, true) return this.client.getrawtransaction(tx.txnTxid, true)
.then(async rtx => { .then(async rtx => {
if (!rtx.blockhash) return null if (!rtx.blockhash) return null
// Transaction is confirmed // Transaction is confirmed
const block = await db.getBlockByHash(rtx.blockhash) const block = await db.getBlockByHash(rtx.blockhash)
if (block && block.blockID) { if (block && block.blockID) {

25
tracker/transaction.js

@ -8,6 +8,7 @@ const _ = require('lodash')
const bitcoin = require('bitcoinjs-lib') const bitcoin = require('bitcoinjs-lib')
const util = require('../lib/util') const util = require('../lib/util')
const Logger = require('../lib/logger') const Logger = require('../lib/logger')
const addrHelper = require('../lib/bitcoin/addresses-helper')
const hdaHelper = require('../lib/bitcoin/hd-accounts-helper') const hdaHelper = require('../lib/bitcoin/hd-accounts-helper')
const db = require('../lib/db/mysql-db-wrapper') const db = require('../lib/db/mysql-db-wrapper')
const network = require('../lib/bitcoin/network') const network = require('../lib/bitcoin/network')
@ -28,9 +29,9 @@ class Transaction {
*/ */
constructor(tx) { constructor(tx) {
this.tx = tx this.tx = tx
this.txid = this.tx.getId() this.txid = this.tx.getId()
// Id of transaction stored in db // Id of transaction stored in db
this.storedTxnID = null this.storedTxnID = null
// Should this transaction be broadcast out to connected clients? // Should this transaction be broadcast out to connected clients?
this.doBroadcast = false this.doBroadcast = false
} }
@ -80,7 +81,7 @@ class Transaction {
// Store database ids of double spend transactions // Store database ids of double spend transactions
const doubleSpentTxnIDs = [] const doubleSpentTxnIDs = []
// Store inputs of interest // Store inputs of interest
const inputs = [] const inputs = []
// Extracts inputs information // Extracts inputs information
let index = 0 let index = 0
@ -153,13 +154,13 @@ class Transaction {
async _processOutputs() { async _processOutputs() {
// Store outputs, keyed by address. Values are arrays of outputs // Store outputs, keyed by address. Values are arrays of outputs
const indexedOutputs = {} const indexedOutputs = {}
// Extracts outputs information // Extracts outputs information
let index = 0 let index = 0
for (let output of this.tx.outs) { for (let output of this.tx.outs) {
try { try {
const address = bitcoin.address.fromOutputScript(output.script, activeNet) const address = addrHelper.outputScript2Address(output.script)
if (!indexedOutputs[address]) if (!indexedOutputs[address])
indexedOutputs[address] = [] indexedOutputs[address] = []
@ -174,7 +175,7 @@ class Transaction {
// Array of addresses receiving tx outputs // Array of addresses receiving tx outputs
const addresses = _.keys(indexedOutputs) const addresses = _.keys(indexedOutputs)
// Store a list of known addresses that received funds // Store a list of known addresses that received funds
let fundedAddresses = [] let fundedAddresses = []
@ -203,7 +204,7 @@ class Transaction {
for (let a of fundedAddresses) { for (let a of fundedAddresses) {
outputs.push({ outputs.push({
txnID: this.storedTxnID, txnID: this.storedTxnID,
addrID: a.addrID, addrID: a.addrID,
outIndex: a.outIndex, outIndex: a.outIndex,
outAmount: a.outAmount, outAmount: a.outAmount,
@ -253,9 +254,9 @@ class Transaction {
// Store a list of known addresses that received funds // Store a list of known addresses that received funds
const fundedAddresses = [] const fundedAddresses = []
const xpubList = _.keys(hdAccounts) const xpubList = _.keys(hdAccounts)
if (xpubList.length > 0) { if (xpubList.length > 0) {
await util.seriesCall(xpubList, async xpub => { await util.parallelCall(xpubList, async xpub => {
const usedNewAddresses = await this._deriveNewAddresses( const usedNewAddresses = await this._deriveNewAddresses(
xpub, xpub,
hdAccounts[xpub], hdAccounts[xpub],
@ -281,7 +282,7 @@ class Transaction {
} }
}) })
} }
return fundedAddresses return fundedAddresses
} }
@ -290,7 +291,7 @@ class Transaction {
* Check if tx addresses are at or beyond the next unused * Check if tx addresses are at or beyond the next unused
* index for the HD chain. Derive additional addresses * index for the HD chain. Derive additional addresses
* to replace the gap limit and add those addresses to * to replace the gap limit and add those addresses to
* the database. Make sure to account for tx sending to * the database. Make sure to account for tx sending to
* newly-derived addresses. * newly-derived addresses.
* *
* @param {string} xpub * @param {string} xpub
@ -389,7 +390,7 @@ class Transaction {
await db.addAddressesToHDAccount(xpub, newAddresses) await db.addAddressesToHDAccount(xpub, newAddresses)
return _.keys(usedNewAddresses) return _.keys(usedNewAddresses)
} }
/** /**
* Store the transaction in database * Store the transaction in database

17
tracker/transactions-bundle.js

@ -9,6 +9,7 @@ const LRU = require('lru-cache')
const bitcoin = require('bitcoinjs-lib') const bitcoin = require('bitcoinjs-lib')
const util = require('../lib/util') const util = require('../lib/util')
const db = require('../lib/db/mysql-db-wrapper') const db = require('../lib/db/mysql-db-wrapper')
const addrHelper = require('../lib/bitcoin/addresses-helper')
const network = require('../lib/bitcoin/network') const network = require('../lib/bitcoin/network')
const keys = require('../keys')[network.key] const keys = require('../keys')[network.key]
const activeNet = network.network const activeNet = network.network
@ -69,11 +70,7 @@ class TransactionsBundle {
// Process transactions by slices of 5000 transactions // Process transactions by slices of 5000 transactions
const MAX_NB_TXS = 5000 const MAX_NB_TXS = 5000
const lists = util.splitList(this.transactions, MAX_NB_TXS) const lists = util.splitList(this.transactions, MAX_NB_TXS)
const results = await util.seriesCall(lists, txs => this._prefilterTransactions(txs))
const results = await util.seriesCall(lists, list => {
return this._prefilterTransactions(list)
})
return _.flatten(results) return _.flatten(results)
} }
@ -110,15 +107,15 @@ class TransactionsBundle {
const txid = tx.getId() const txid = tx.getId()
indexedTxs[txid] = i indexedTxs[txid] = i
// If we already checked this tx // If we already checked this tx
if (TransactionsBundle.cache.has(txid)) if (TransactionsBundle.cache.has(txid))
continue continue
for (const j in tx.outs) { for (const j in tx.outs) {
try { try {
const script = tx.outs[j].script const script = tx.outs[j].script
const address = bitcoin.address.fromOutputScript(script, activeNet) const address = addrHelper.outputScript2Address(script)
outputs.push(address) outputs.push(address)
// Index the output // Index the output
if (!indexedOutputs[address]) if (!indexedOutputs[address])
@ -174,7 +171,9 @@ class TransactionsBundle {
} }
// Prefilter // Prefilter
const inRes = await db.getOutputSpends(inputs) const lists = util.splitList(inputs, 1000)
const results = await util.parallelCall(lists, list => db.getOutputSpends(list))
const inRes = _.flatten(results)
for (const i in inRes) { for (const i in inRes) {
const key = inRes[i].txnTxid + '-' + inRes[i].outIndex const key = inRes[i].txnTxid + '-' + inRes[i].outIndex

Loading…
Cancel
Save