diff --git a/lib/bcoin.js b/lib/bcoin.js index a7456309..ad09ee58 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -10,6 +10,8 @@ var bn = require('bn.js'); var hash = require('hash.js'); var async = require('async'); +bcoin.debug = +process.env.BCOIN_DEBUG === 1; + bcoin.ecdsa = elliptic.ec('secp256k1'); bcoin.utils = require('./bcoin/utils'); bcoin.bloom = require('./bcoin/bloom'); diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 3a9c40d1..e7ea44e6 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -223,37 +223,26 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() { return merkleTree[merkleTree.length - 1]; }; -Block.prototype._debug = function debug() { - var args = Array.prototype.slice.call(arguments); - - if (!this.chain) - return; - - args.unshift('debug'); - - return this.chain.emit.apply(this.chain, args); -}; - Block.prototype._verify = function _verify() { var uniq = {}; var i, tx, hash; // Check proof of work if (!utils.testTarget(this.bits, this.hash())) { - this._debug('Block failed POW test: %s', this.rhash); + utils.debug('Block failed POW test: %s', this.rhash); return false; } // Check timestamp against now + 2 hours if (this.ts > utils.now() + 2 * 60 * 60) { - this._debug('Block timestamp is too high: %s', this.rhash); + utils.debug('Block timestamp is too high: %s', this.rhash); return false; } // Verify the partial merkle tree if we are a merkleblock. if (this.subtype === 'merkleblock') { if (!this._verifyPartial()) { - this._debug('Block failed merkle test: %s', this.rhash); + utils.debug('Block failed merkle test: %s', this.rhash); return false; } } @@ -265,13 +254,13 @@ Block.prototype._verify = function _verify() { // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize || this.size() > constants.block.maxSize) { - this._debug('Block is too large: %s', this.rhash); + utils.debug('Block is too large: %s', this.rhash); return false; } // First TX must be a coinbase if (!this.txs.length || !this.txs[0].isCoinbase()) { - this._debug('Block has no coinbase: %s', this.rhash); + utils.debug('Block has no coinbase: %s', this.rhash); return false; } @@ -281,14 +270,14 @@ Block.prototype._verify = function _verify() { // The rest of the txs must not be coinbases if (i > 0 && tx.isCoinbase()) { - this._debug('Block more than one coinbase: %s', this.rhash); + utils.debug('Block more than one coinbase: %s', this.rhash); return false; } // Check for duplicate txids hash = tx.hash('hex'); if (uniq[hash]) { - this._debug('Block has duplicate txids: %s', this.rhash); + utils.debug('Block has duplicate txids: %s', this.rhash); return false; } uniq[hash] = true; @@ -296,7 +285,7 @@ Block.prototype._verify = function _verify() { // Check merkle root if (this.getMerkleRoot() !== this.merkleRoot) { - this._debug('Block failed merkleroot test: %s', this.rhash); + utils.debug('Block failed merkleroot test: %s', this.rhash); return false; } @@ -321,7 +310,7 @@ Block.prototype.verifyContext = function verifyContext() { // Ensure it's not an orphan if (!prev) { - this._debug('Block has no previous entry: %s', this.rhash); + utils.debug('Block has no previous entry: %s', this.rhash); return false; } @@ -329,41 +318,41 @@ Block.prototype.verifyContext = function verifyContext() { // Ensure the timestamp is correct if (this.ts <= prev.getMedianTime()) { - this._debug('Block time is lower than median: %s', this.rhash); + utils.debug('Block time is lower than median: %s', this.rhash); return false; } // Ensure the miner's target is equal to what we expect if (this.bits !== this.chain.target(prev, this)) { - this._debug('Block is using wrong target: %s', this.rhash); + utils.debug('Block is using wrong target: %s', this.rhash); return false; } // Only allow version 2 blocks (coinbase height) // once the majority of blocks are using it. if (this.version < 2 && prev.isOutdated(2)) { - this._debug('Block is outdated (v2): %s', this.rhash); + utils.debug('Block is outdated (v2): %s', this.rhash); return false; } // Only allow version 3 blocks (sig validation) // once the majority of blocks are using it. if (this.version < 3 && prev.isOutdated(3)) { - this._debug('Block is outdated (v3): %s', this.rhash); + utils.debug('Block is outdated (v3): %s', this.rhash); return false; } // Only allow version 4 blocks (checklocktimeverify) // once the majority of blocks are using it. if (this.version < 4 && prev.isOutdated(4)) { - this._debug('Block is outdated (v4): %s', this.rhash); + utils.debug('Block is outdated (v4): %s', this.rhash); return false; } // Only allow version 8 blocks (locktime median past) // once the majority of blocks are using it. // if (this.version < 8 && prev.isOutdated(8)) { - // this._debug('Block is outdated (v8): %s', this.rhash); + // utils.debug('Block is outdated (v8): %s', this.rhash); // return false; // } @@ -373,13 +362,13 @@ Block.prototype.verifyContext = function verifyContext() { // Make sure the coinbase is parseable. if (!cb) { - this._debug('Block has malformed coinbase: %s', this.rhash); + utils.debug('Block has malformed coinbase: %s', this.rhash); return false; } // Make sure coinbase height is equal to the actual height. if (cb.height !== height) { - this._debug('Block has bad coinbase height: %s', this.rhash); + utils.debug('Block has bad coinbase height: %s', this.rhash); return false; } } @@ -417,7 +406,7 @@ Block.prototype.verifyContext = function verifyContext() { // Transactions must be finalized with // regards to nSequence and nLockTime. if (!tx.isFinal(height, ts)) { - this._debug('TX is not final: %s (%s)', this.rhash, i); + utils.debug('TX is not final: %s (%s)', this.rhash, i); return false; } @@ -427,7 +416,7 @@ Block.prototype.verifyContext = function verifyContext() { // if (tx.sigops(true) > constants.script.maxTxSigops) { // // Block 71036 abused checksig to // // include a huge number of sigops. - // this._debug('Block TX has too many sigops: %s', this.rhash); + // utils.debug('Block TX has too many sigops: %s', this.rhash); // if (!(network.type === 'main' && height === 71036)) // return false; // } @@ -441,7 +430,7 @@ Block.prototype.verifyContext = function verifyContext() { sigops += tx.sigops(); if (sigops > constants.script.maxBlockSigops) { - this._debug('Block has too many sigops: %s', this.rhash); + utils.debug('Block has too many sigops: %s', this.rhash); return false; } @@ -450,7 +439,7 @@ Block.prototype.verifyContext = function verifyContext() { // Blocks 91842 and 91880 created duplicate // txids by using the same exact output script // and extraNonce. - this._debug('Block is overwriting txids: %s', this.rhash); + utils.debug('Block is overwriting txids: %s', this.rhash); if (!(network.type === 'main' && (height === 91842 || height === 91880))) return false; } @@ -472,13 +461,13 @@ Block.prototype.verifyContext = function verifyContext() { // Verify the script if (!tx.verify(j, true, flags)) { - this._debug('Block has invalid inputs: %s', this.rhash); + utils.debug('Block has invalid inputs: %s', this.rhash); return false; } // Ensure tx is not double spending an output // if (this.chain.isSpent(input.out.hash, input.out.index)) { - // this._debug('Block is using spent inputs: %s', this.rhash); + // utils.debug('Block is using spent inputs: %s', this.rhash); // return false; // } } diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 76592b8d..8ec39958 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -29,6 +29,9 @@ function Chain(options) { this.storage = this.options.storage; this.strict = this.options.strict || false; + if (this.options.debug) + bcoin.debug = true; + this.tip = null; this.orphan = { @@ -132,7 +135,7 @@ Chain.prototype._init = function _init() { } utils.nextTick(function() { - self.emit('debug', 'Chain is loading.'); + utils.debug('Chain is loading.'); }); s = this.storage.createReadStream({ @@ -152,7 +155,7 @@ Chain.prototype._init = function _init() { s.on('end', function() { self.loading = false; self.emit('load'); - self.emit('debug', 'Chain successfully loaded.'); + utils.debug('Chain successfully loaded.'); }); }; @@ -472,7 +475,7 @@ Chain.prototype.add = function add(block, peer) { if (code !== Chain.codes.okay) { if (!(this.options.multiplePeers && code === Chain.codes.newOrphan)) - this.emit('debug', 'Chain Error: %s', Chain.msg(code)); + utils.debug('Chain Error: %s', Chain.msg(code)); } return total; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 3d6df110..ff720a5d 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -66,17 +66,17 @@ Miner.prototype._init = function _init() { // }); this.on('block', function(block) { - self.chain.emit('debug', + utils.debug( 'Found block: %d (%s)', block.height, block.hash('hex')); // Emit the block hex as a failsafe (in case we can't send it) - self.chain.emit('debug', 'Block: %s', utils.toHex(block.render())); + utils.debug('Block: %s', utils.toHex(block.render())); self.pool.sendBlock(block); }); this.on('status', function(stat) { - self.chain.emit('debug', + utils.debug( 'hashrate=%dkhs hashes=%d target=%d height=%d best=%s', stat.hashrate / 1000 | 0, stat.hashes, @@ -294,12 +294,12 @@ Miner.prototype.iterate = function iterate() { // Make sure our block is valid if (!self.block.verify()) - return self.emit('debug', '%s did not verify.', hash); + return utils.debug('%s did not verify.', hash); // Add our block to the chain res = self.chain.add(self.block); if (res > 0) - return self.emit('debug', '%s could not be added to chain.', hash); + return utils.debug('%s could not be added to chain.', hash); // Emit our newly found block self.emit('block', self.block); diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 0ad5742d..8511d5aa 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -139,7 +139,7 @@ Peer.prototype._init = function init() { if (this.pool.options.fullNode) { this.once('version', function() { - self.pool.emit('debug', + utils.debug( 'Sent version (%s): height=%s', self.host, this.pool.chain.height()); }); @@ -475,7 +475,7 @@ Peer.prototype._handleAddr = function handleAddr(addrs) { }); }, this); - this.pool.emit('debug', + utils.debug( 'Recieved %d peers (seeds=%d, peers=%d).', addrs.length, this.pool.seeds.length, @@ -575,10 +575,10 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) { }; Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) { - this.emit('debug', + utils.debug( 'Requesting headers packet from %s with getheaders', this.host); - this.pool.emit('debug', 'Height: %s, Hash: %s, Stop: %s', + utils.debug('Height: %s, Hash: %s, Stop: %s', this.pool.chain.getHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); @@ -586,10 +586,10 @@ Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) { }; Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) { - this.emit('debug', + utils.debug( 'Requesting inv packet from %s with getblocks', this.host); - this.pool.emit('debug', 'Height: %s, Hash: %s, Stop: %s', + utils.debug('Height: %s, Hash: %s, Stop: %s', this.pool.chain.getHeight(hashes[0]), hashes ? utils.revHex(hashes[0]) : 0, stop ? utils.revHex(stop) : 0); @@ -603,14 +603,14 @@ Peer.prototype.loadItems = function loadItems(hashes, stop) { }; Peer.prototype.loadMempool = function loadMempool() { - this.emit('debug', + utils.debug( 'Requesting inv packet from %s with mempool', this.host); this._write(this.framer.mempool()); }; Peer.prototype.reject = function reject(details) { - this.emit('debug', + utils.debug( 'Sending reject packet to %s', this.host); this._write(this.framer.reject(details)); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 5b3e18b8..fb1111bf 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -29,6 +29,9 @@ function Pool(options) { this.options = options || {}; + if (this.options.debug) + bcoin.debug = true; + if (this.options.network) network.set(this.options.network); @@ -154,11 +157,6 @@ function Pool(options) { this.options.wallets = this.options.wallets || []; this.wallets = []; - this.chain.on('debug', function() { - var args = Array.prototype.slice.call(arguments); - self.emit.apply(self, ['debug'].concat(args)); - }); - Pool.global = this; this.loading = true; @@ -186,7 +184,7 @@ Pool.prototype._init = function _init() { }); this.chain.on('fork', function(data, peer) { - self.emit('debug', + utils.debug( 'Fork at height %d: expected=%s received=%s checkpoint=%s peer=%s', data.height, utils.revHex(data.expected), @@ -202,7 +200,7 @@ Pool.prototype._init = function _init() { }); this.chain.on('invalid', function(data, peer) { - self.emit('debug', + utils.debug( 'Invalid block at height: %d: hash=%s peer=%s', data.height, utils.revHex(data.hash), @@ -293,12 +291,12 @@ Pool.prototype.createConnection = function createConnection(peer, pool, options) socket = net.connect(addr.port, addr.host); } - pool.emit('debug', + utils.debug( 'Connecting to %s:%d (priority=%s)', addr.host, addr.port, options.priority); socket.on('connect', function() { - pool.emit('debug', + utils.debug( 'Connected to %s:%d (priority=%s)', addr.host, addr.port, options.priority); }); @@ -319,7 +317,7 @@ Pool.prototype._addLoader = function _addLoader() { peer = this._createPeer(750 * Math.random(), true); peer.once('socket', function() { - self.emit('debug', 'Added loader peer: %s', peer.host); + utils.debug('Added loader peer: %s', peer.host); }); this.peers.load = peer; @@ -420,7 +418,7 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) { if (headers.length === 0) return; - this.emit('debug', + utils.debug( 'Recieved %s headers from %s', headers.length, peer.host); @@ -475,7 +473,7 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) { if (hashes.length === 0) return; - this.emit('debug', + utils.debug( 'Recieved %s block hashes from %s', hashes.length, peer.host); @@ -494,7 +492,7 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) { // Make sure the peer doesn't send us // more than 200 orphans every 3 minutes. if (this.orphaning(peer)) { - this.emit('debug', 'Peer is orphaning (%s)', peer.host); + utils.debug('Peer is orphaning (%s)', peer.host); this.misbehaving(peer, 100); return; } @@ -566,7 +564,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { // Ensure the block was not invalid last time. // Someone might be sending us bad blocks to DoS us. if (this.block.invalid[block.hash('hex')]) { - this.emit('debug', 'Peer is sending an invalid chain (%s)', peer.host); + utils.debug('Peer is sending an invalid chain (%s)', peer.host); this.misbehaving(peer, 100); return false; } @@ -574,7 +572,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { // Ensure this is not a continuation // of an invalid chain. if (this.block.invalid[block.prevBlock]) { - this.emit('debug', + utils.debug( 'Peer is sending an invalid continuation chain (%s)', peer.host); this.misbehaving(peer, 100); @@ -583,14 +581,14 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { // Ignore if we already have. if (this.chain.has(block)) { - this.emit('debug', 'Already have block %s (%s)', block.height, peer.host); + utils.debug('Already have block %s (%s)', block.height, peer.host); this.misbehaving(peer, 1); return false; } // Make sure the block is valid. if (!block.verify()) { - this.emit('debug', + utils.debug( 'Block verification failed for %s (%s)', block.rhash, peer.host); this.block.invalid[block.hash('hex')] = true; @@ -601,7 +599,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { // Someone is sending us blocks without // us requesting them. if (!requested) { - this.emit('debug', + utils.debug( 'Recieved unrequested block: %s (%s)', block.rhash, peer.host); } @@ -616,7 +614,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { // Make sure the peer doesn't send us // more than 200 orphans every 3 minutes. if (this.orphaning(peer)) { - this.emit('debug', 'Peer is orphaning (%s)', peer.host); + utils.debug('Peer is orphaning (%s)', peer.host); this.misbehaving(peer, 100); return false; } @@ -634,7 +632,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { this.chain.getOrphanRoot(block) ); - this.emit('debug', 'Handled orphan %s (%s)', block.rhash, peer.host); + utils.debug('Handled orphan %s (%s)', block.rhash, peer.host); return false; } @@ -749,15 +747,10 @@ Pool.prototype._createPeer = function _createPeer(backoff, priority) { self.emit('error', err, peer); }); - peer.on('debug', function() { - var args = Array.prototype.slice.call(arguments); - self.emit.apply(self, ['debug'].concat(args)); - }); - peer.on('reject', function(payload) { var data = utils.revHex(utils.toHex(payload.data)); - self.emit('debug', + utils.debug( 'Reject: msg=%s ccode=%s reason=%s data=%s', payload.message, payload.ccode, @@ -800,7 +793,7 @@ Pool.prototype._createPeer = function _createPeer(backoff, priority) { if (version.height > self.block.bestHeight) self.block.bestHeight = version.height; self.emit('version', version, peer); - self.emit('debug', + utils.debug( 'Received version from %s: version=%d height=%d agent=%s', peer.host, peer.version.v, peer.version.height, peer.version.agent); }); @@ -907,7 +900,7 @@ Pool.prototype._removePeer = function _removePeer(peer) { this.peers.all.splice(i, 1); if (this.peers.load === peer) { - this.emit('debug', 'Removed loader peer (%s).', peer.host); + utils.debug('Removed loader peer (%s).', peer.host); this.peers.load = null; } }; @@ -1129,8 +1122,8 @@ Pool.prototype.searchWallet = function(w) { } utils.nextTick(function() { - self.emit('debug', 'Wallet time: %s', new Date(ts * 1000)); - self.emit('debug', + utils.debug('Wallet time: %s', new Date(ts * 1000)); + utils.debug( 'Reverted chain to height=%d (%s)', self.chain.height(), new Date(self.chain.getTip().ts * 1000) @@ -1374,7 +1367,7 @@ Pool.prototype._doRequests = function _doRequests() { return item.start(this.peers.load); }, this); - this.emit('debug', + utils.debug( 'Requesting %s/%s items from %s with getdata', req.length, this.request.queue.length, @@ -1467,7 +1460,7 @@ Pool.prototype.sendTX = function sendTX(tx) { // isStandardInputs as well. if (tx.full()) { if (!tx.verify(null, true)) { - this.emit('debug', + utils.debug( 'Could not relay TX (%s). It does not verify.', tx.rhash); return; @@ -1637,7 +1630,7 @@ Pool.prototype.usableSeed = function usableSeed(priority, connecting) { // This should never happen: priority sockets // should _always_ get an address. if (priority) { - this.emit('debug', + utils.debug( 'We had to connect to a random peer. Something is not right.'); return seeds[Math.random() * (seeds.length - 1) | 0]; @@ -1718,7 +1711,7 @@ Pool.prototype.misbehaving = function misbehaving(peer, dos) { if (peer._banscore >= constants.banScore) { this.peers.misbehaving[peer.host] = utils.now(); - this.emit('debug', 'Ban threshold exceeded for %s', peer.host); + utils.debug('Ban threshold exceeded for %s', peer.host); peer.destroy(); return true; } diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index bce204b5..66600367 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -998,6 +998,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) { } default: { // Unknown operation + utils.debug('Unknown opcode "%s" at offset %d', o, pc); return false; } } @@ -1222,7 +1223,25 @@ script.size = function size(s) { }; script.isEncoded = function isEncoded(s) { - return utils.isBytes(s); + var i, b; + + if (!Array.isArray(data)) + return false; + + for (i = 0; i < data.length; i++) { + b = data[i]; + + if (typeof b !== 'number') + return false; + + if (constants.opcodesByVal[b] == null) { + if (b >= 0x01 && b <= 0x4b) + continue; + return false; + } + } + + return true; }; script._lockTime = function _lockTime(s) { @@ -1725,6 +1744,10 @@ script.isScripthashInput = function isScripthashInput(s, data, strict) { if (script.isKeyEncoding(raw)) return false; + // Ensure this is a valid encoded script + // if (!script.isEncoded(raw)) + // return false; + // Check data against last array in case // a raw redeem script was passed in. if (data && utils.isEqual(data, raw)) diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 62a1463e..b8ad22d7 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -711,21 +711,29 @@ utils.isBytes = function isBytes(data) { return true; }; -utils._inspect = function inspect(obj) { +utils._inspect = function _inspect(obj, color) { return typeof obj !== 'string' - ? util.inspect(obj, null, 20, true) + ? util.inspect(obj, null, 20, color !== false) : obj; }; utils.print = function print(msg) { return typeof msg === 'object' - ? process.stdout.write(utils._inspect(msg) + '\n') + ? process.stdout.write(utils._inspect(msg, !!process.stdout.isTTY) + '\n') : console.log.apply(console, arguments); }; utils.debug = function debug() { - var args = Array.prototype.slice.call(arguments); - args[0] = '\x1b[31m' + args[0] + '\x1b[m'; + var args; + + if (!bcoin.debug) + return; + + args = Array.prototype.slice.call(arguments); + + // if (typeof args[0] === 'string' && process.stdout.isTTY) + // args[0] = '\x1b[32m' + args[0] + '\x1b[m'; + return utils.print.apply(null, args); }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 07db60a7..75da1b35 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -122,7 +122,7 @@ function Wallet(options, passphrase) { }, this); if (this.redeem) { - if (!bcoin.script.isEncoded(this.redeem)) + if (!utils.isBytes(this.redeem)) this.redeem = bcoin.script.encode(this.redeem); this.type = 'scripthash'; this.subtype = null;