From 52b653083cd49e0fb60f4f8aa5e6d00320d03dc6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 21 Dec 2015 19:41:15 -0800 Subject: [PATCH] add features potentially useful for blockchain explorers. --- lib/bcoin/block.js | 62 +++++++++++++- lib/bcoin/chain.js | 37 ++++++++- lib/bcoin/peer.js | 17 ++-- lib/bcoin/pool.js | 2 +- lib/bcoin/tx.js | 203 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 268 insertions(+), 53 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index bb2f4471..e8ef2354 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -31,10 +31,15 @@ function Block(data, subtype) { return utils.toHex(hash); }); this.flags = data.flags || []; - this._network = data._network || false; + this._raw = data._raw || null; this._size = data._size || 0; + this.network = data.network || false; + this.relayedBy = data.relayedBy || '0.0.0.0'; + this._height = data._height != null ? data._height : -1; + this._nextBlock = data._nextBlock || null; + // List of matched TXs this.tx = []; this.invalid = false; @@ -42,7 +47,8 @@ function Block(data, subtype) { if (this.subtype === 'block') { this.txs = data.txs || []; this.txs = this.txs.map(function(tx) { - tx._network = self._network; + tx.network = self.network; + tx.relayedBy = self.relayedBy; tx = bcoin.tx(tx); tx.block = self.hash('hex'); tx.ts = tx.ts || self.ts; @@ -69,7 +75,7 @@ Block.prototype.hash = function hash(enc) { }; Block.prototype.abbr = function abbr() { - if (this._network) + if (this.network) return this._raw.slice(); var res = new Array(80); @@ -239,6 +245,49 @@ Block.prototype._checkBlock = function checkBlock() { return true; }; +Block.prototype.__defineSetter__('height', function(height) { + return this._height = height; +}); + +Block.prototype.__defineGetter__('height', function() { + return this.getHeight(bcoin.chain.global); +}); + +Block.prototype.__defineGetter__('nextBlock', function() { + return this.getNextBlock(bcoin.chain.global); +}); + +Block.prototype.getHeight = function getHeight(chain) { + if (this._height >= 0) + return this._height; + + chain = chain || bcoin.chain.global; + + if (!chain) + return -1; + + return this._height = chain.getHeight(this.hash('hex')); +}; + +Block.prototype.getNextBlock = function getNextBlock(chain) { + var next; + + if (this._nextBlock) + return this._nextBlock; + + chain = chain || bcoin.chain.global; + + if (!chain) + return utils.toHex(constants.protocol.zeroHash); + + next = chain.getNextBlock(this.hash('hex')); + + if (!next) + return utils.toHex(constants.zeroHash); + + return this._nextBlock = next; +}; + Block.prototype.toJSON = function toJSON() { return { v: '1', @@ -247,6 +296,9 @@ Block.prototype.toJSON = function toJSON() { hash: this.hash('hex'), prevBlock: this.prevBlock, ts: this.ts, + network: this.network, + relayedBy: this.relayedBy, + _height: this._height, block: utils.toHex(bcoin.protocol.framer.block(this, this.subtype)) }; }; @@ -265,7 +317,9 @@ Block.fromJSON = function fromJSON(json) { ? parser.parseMerkleBlock(raw) : parser.parseBlock(raw); - data._network = json._network; + data.network = json.network; + data.relayedBy = json.relayedBy; + data._height = json._height; block = new Block(data, json.subtype); diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index e394d953..36e84d8c 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -30,17 +30,19 @@ function Chain(options) { this.storage = this.options.storage; this.strict = this.options.strict || false; this.cacheLimit = this.options.cacheLimit || 2000; + this.block = { list: [], - // Bloom filter for all known blocks bloom: new bcoin.bloom(8 * 1024 * 1024, 16, 0xdeadbeef) }; + this.orphan = { map: {}, bmap: {}, count: 0 }; + this.index = { bloom: null, hashes: [], @@ -48,6 +50,7 @@ function Chain(options) { heights: [], lastTs: 0 }; + this.request = new utils.RequestCache(); // Start from the genesis block @@ -67,6 +70,8 @@ function Chain(options) { // Last TS after preload, needed for fill percent this.index.lastTs = this.index.ts[this.index.ts.length - 1]; + Chain.global = this; + this.loading = false; this._init(); } @@ -591,6 +596,36 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { return root; }; +Chain.prototype.getHeight = function getHeight(hash) { + var chain, i, height; + + if (Array.isArray(hash)) + hash = utils.toHex(hash); + else if (hash.hash) + hash = hash.hash('hex'); + + i = this.index.hashes.indexOf(hash); + height = this.index.heights[i]; + + if (height === null) + return -1; + + return height; +}; + +Chain.prototype.getNextBlock = function getNextBlock(hash) { + var height, nextHeight, i; + + height = this.getHeight(chain); + nextHeight = this.index.heights[i + 1]; + i = this.index.heights.indexOf(nextHeight); + + if (nextHeight !== height + 1) + return null; + + return this.index.hashes[i] || null; +}; + Chain.prototype.toJSON = function toJSON() { var keep = 1000; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 75d70896..ac0b6423 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -79,6 +79,10 @@ function Peer(pool, createSocket, options) { inherits(Peer, EventEmitter); +Peer.prototype.__defineGetter__('address', function() { + return this.socket && (this.socket.remoteAddress || '0.0.0.0'); +}); + Peer.prototype._init = function init() { var self = this; @@ -108,10 +112,9 @@ Peer.prototype._init = function init() { if (this.pool.options.fullNode) { this.once('version', function() { - var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; self.pool.emit('debug', 'Sent version (%s): height=%s', - ip, this.pool.chain.getStartHeight()); + self.address, this.pool.chain.getStartHeight()); // self.pool.emit('debug', 'version (%s): sending locator hashes', ip); // self.loadBlocks(self.chain.locatorHashes(), 0); }); @@ -143,9 +146,7 @@ Peer.prototype.startSync = function startSync() { if (!this.pool.options.fullNode) return; - var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; - - this.pool.emit('debug', 'version (%s): sending locator hashes', ip); + this.pool.emit('debug', 'version (%s): sending locator hashes', this.address); this.loadBlocks(this.chain.locatorHashes(), 0); }; @@ -366,11 +367,13 @@ Peer.prototype._onPacket = function onPacket(packet) { return this._handleGetAddr(); if (cmd === 'merkleblock' || cmd === 'block') { - payload._network = true; + payload.network = true; + payload.relayedBy = this.address; payload = bcoin.block(payload, cmd); this.lastBlock = payload; } else if (cmd === 'tx') { - payload._network = true; + payload.network = true; + payload.relayedBy = this.address; payload = bcoin.tx(payload, this.lastBlock); } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 97983d7c..de323a55 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -499,7 +499,7 @@ Pool.prototype.bestPeer = function bestPeer() { }); if (best) - this.emit('debug', 'Best peer: %s', best.socket.remoteAddress); + this.emit('debug', 'Best peer: %s', best.address); return best; }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index f5281cd4..a7347600 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -29,11 +29,15 @@ function TX(data, block) { this.lock = data.lock || 0; this.ts = data.ts || 0; this.block = null; - this._hash = null; + this._raw = data._raw || null; - this._network = data._network || false; this._size = data._size || 0; + + this.network = data.network || false; + this.relayedBy = data.relayedBy || '0.0.0.0'; + this._height = data._height != null ? data._height : -1; + this._lock = this.lock; if (data.inputs) { @@ -71,7 +75,7 @@ TX.prototype.hash = function hash(enc) { }; TX.prototype.render = function render() { - if (this._network) + if (this.network) return this._raw.slice(); return bcoin.protocol.framer.tx(this); }; @@ -887,30 +891,64 @@ TX.prototype.inputAddrs = function inputAddrs() { }; TX.getInputData = function getInputData(input) { - if (!input || !input.script) return; + if (!input || !input.script) + return; - var script = input.script; + var s = input.script; var sig, pub, hash, addr, redeem, data, output; - if (bcoin.script.isPubkeyhashInput(script)) { - sig = utils.toHex(script[0]); - pub = script[1]; + if (!input.out) { + return { + addr: 'Unknown', + hash: 'Unknown', + value: new bn(0), + script: s, + seq: input.seq, + none: true + }; + } + + if (+input.out.hash === 0) { + return { + addr: 'Coinbase', + hash: 'Coinbase', + value: new bn(0), + script: s, + seq: input.seq, + none: true + }; + } + + if (input.out.tx) { + output = input.out.tx.outputs[input.out.index]; + return TX.getOutputData(output); + } + + if (bcoin.script.isPubkeyhashInput(s)) { + sig = utils.toHex(s[0]); + pub = s[1]; hash = utils.ripesha(pub); addr = bcoin.wallet.hash2addr(hash); return { sig: sig, pub: pub, hash: hash, - addr: addr + addr: addr, + value: new bn(0), + script: s, + seq: input.seq }; } - if (bcoin.script.isScripthashInput(script)) { - pub = script[script.length - 1]; + if (bcoin.script.isScripthashInput(s)) { + pub = s[s.length - 1]; hash = utils.ripesha(pub); addr = bcoin.wallet.hash2addr(hash, 'scripthash'); redeem = bcoin.script.decode(pub); - data = TX.getOutputData({ script: redeem }); + data = TX.getOutputData({ + script: redeem, + value: new bn(0) + }); data.pub = pub; data.hash = hash; data.addr = addr; @@ -923,49 +961,61 @@ TX.getInputData = function getInputData(input) { n: data.multisig.n, keys: data.multisig.keys, hashes: data.multisig.hashes, - addrs: data.multisig.addrs + addrs: data.multisig.addrs, + script: redeem }; + data.script = s; + data.seq = input.seq; return data; } - if (!input.out.tx) - return; - - output = input.out.tx.outputs[input.out.index]; - - return TX.getOutputData(output); + return { + addr: 'Unknown', + hash: 'Unknown', + value: new bn(0), + script: s, + seq: input.seq, + none: true + }; }; TX.getOutputData = function getOutputData(output) { if (!output || !output.script) return; - var script = output.script; + var s = output.script; + var lock = bcoin.script.lockTime(s); var pub, hash, addr, pubs, ret; - if (bcoin.script.isPubkey(script)) { - pub = script[0]; + if (bcoin.script.isPubkey(s)) { + pub = s[0]; hash = utils.ripesha(pub); addr = bcoin.wallet.hash2addr(hash); return { sig: null, pub: pub, hash: hash, - addr: addr + addr: addr, + value: output.value, + script: s, + lock: lock }; } - if (bcoin.script.isPubkeyhash(script)) { - hash = script[2]; + if (bcoin.script.isPubkeyhash(s)) { + hash = s[2]; addr = bcoin.wallet.hash2addr(hash); return { sig: null, pub: null, hash: hash, - addr: addr + addr: addr, + value: output.value, + script: s, + lock: lock }; } - pubs = bcoin.script.isMultisig(script); + pubs = bcoin.script.isMultisig(s); if (pubs) { hash = utils.ripesha(pubs[0]); addr = bcoin.wallet.hash2addr(hash); @@ -976,21 +1026,24 @@ TX.getOutputData = function getOutputData(output) { addr: addr, keys: pubs, multisig: { - m: new bn(script[0]).toNumber(), - n: new bn(script[script.length - 2]).toNumber(), - keys: keys, - hashes: keys.map(function(key) { + m: new bn(s[0]).toNumber(), + n: new bn(s[s.length - 2]).toNumber(), + keys: pubs, + hashes: pubs.map(function(key) { return utils.ripesha(key); }), - addrs: keys.map(function(key) { + addrs: pubs.map(function(key) { var hash = utils.ripesha(key); return bcoin.wallet.hash2addr(hash); }) - } + }, + value: output.value, + script: s, + lock: lock }; } - if (bcoin.script.isScripthash(script)) { + if (bcoin.script.isScripthash(s)) { hash = utils.toHex(s[1]); addr = bcoin.wallet.hash2addr(hash, 'scripthash'); return { @@ -1003,17 +1056,35 @@ TX.getOutputData = function getOutputData(output) { pub: null, hash: hash, addr: addr - } + }, + value: output.value, + script: s, + lock: lock }; } - if (bcoin.script.isColored(script)) { - ret = bcoin.script.colored(script); + if (bcoin.script.isColored(s)) { + ret = bcoin.script.colored(s); return { + addr: 'OP_RETURN', + hash: 'OP_RETURN', data: ret, - text: utils.array2ascii(ret) + text: utils.array2ascii(ret), + value: output.value, + script: s, + lock: lock, + none: true }; } + + return { + addr: '[Unknown]', + hash: '[Unknown]', + value: new bn(0), + script: s, + lock: lock, + none: true + }; }; TX.prototype.getFee = function getFee() { @@ -1050,6 +1121,49 @@ TX.prototype.funds = function funds(side) { return acc; }; +TX.prototype.__defineSetter__('height', function(height) { + return this._height = height; +}); + +TX.prototype.__defineGetter__('height', function() { + return this.getHeight(bcoin.chain.global); +}); + +TX.prototype.__defineGetter__('confirmations', function() { + return this.getConfirmations(bcoin.chain.global); +}); + +TX.prototype.getHeight = function getHeight(chain) { + if (this._height >= 0) + return this._height; + + chain = chain || bcoin.chain.global; + + if (!chain) + return -1; + + this._height = this.block ? chain.getHeight(this.block) : -1; + + return this._height; +}; + +TX.prototype.getConfirmations = function getConfirmations(chain) { + var top, height; + + chain = chain || bcoin.chain.global; + + if (!chain) + return 0; + + top = chain.index.heights[chain.index.heights.length - 1]; + height = this.getHeight(chain); + + if (height === -1) + return 0; + + return top - height + 1; +}; + TX.prototype.toJSON = function toJSON() { // Compact representation return { @@ -1058,16 +1172,25 @@ TX.prototype.toJSON = function toJSON() { ts: this.ts, ps: this.ps, block: this.block, + network: this.network, + relayedBy: this.relayedBy, tx: utils.toHex(this.render()) }; }; TX.fromJSON = function fromJSON(json) { + var raw, data, tx; + assert.equal(json.v, 1); assert.equal(json.type, 'tx'); - var raw = utils.toArray(json.tx, 'hex'); - var tx = new TX(new bcoin.protocol.parser().parseTX(raw)); + raw = utils.toArray(json.tx, 'hex'); + data = new bcoin.protocol.parser().parseTX(raw); + + data.network = json.network; + data.relayedBy = json.relayedBy; + + tx = new TX(data); tx.ts = json.ts; tx.block = json.block || null; tx.ps = json.ps;