diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 2b4d67f0..ad581d40 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -23,6 +23,22 @@ function Block(data, subtype) { this.tx = []; this.invalid = false; + if (this.subtype === 'block') { + var self = this; + this.txs = data.txs || []; + this.txs = this.txs.map(function(tx) { + tx = bcoin.tx(tx); + tx.block = self.hash('hex'); + tx.ts = tx.ts || self.ts; + return tx; + }); + this.hashes = this.txs.map(function(tx) { + return tx.hash('hex'); + }); + this.tx = this.hashes.slice(); + this.invalid = !this._checkBlock(); + } + this._hash = null; // Verify partial merkle tree and fill `ts` array @@ -64,6 +80,10 @@ Block.prototype.hasTX = function hasTX(hash) { Block.prototype._verifyMerkle = function verifyMerkle() { var height = 0; + if (this.subtype === 'block') { + return; + } + // Count leafs for (var i = this.totalTX; i > 0; i >>= 1) height++; @@ -138,3 +158,65 @@ Block.fromJSON = function fromJSON(json) { return block; }; + +Block.prototype._buildMerkle = function buildMerkle() { + var merkleTree = []; + for (var i = 0; i < this.txs.length; i++) { + merkleTree.push(this.txs[i].hash('hex')); + } + var j = 0; + for (var size = this.txs.length; size > 1; size = ((size + 1) / 2) | 0) { + for (var i = 0; i < size; i += 2) { + var i2 = Math.min(i + 1, size - 1); + var hash = utils.dsha256(merkleTree[j+i] + merkleTree[j+i2], 'hex'); + merkleTree.push(utils.toHex(hash)); + } + j += size; + } + return merkleTree; +}; + +// This mimics the behavior of CheckBlockHeader() +// and CheckBlock() in bitcoin/src/main.cpp. +Block.prototype._checkBlock = function checkBlock() { + // Check proof of work matches claimed amount + if (!utils.testTarget(this.bits, this.hash())) { + return false; + } + + // Check timestamp + if (this.ts > (Date.now() / 1000) + 2 * 60 * 60) { + return false; + } + + // Size of all txs cant be bigger than MAX_BLOCK_SIZE + if (this.txs.length > bcoin.protocol.constants.block.maxSize) { + return false; + } + + // First TX must be a coinbase + if (this.txs[0].inputs.length !== 1 || +this.txs[0].inputs[0].out.hash !== 0) { + return false; + } + + // The rest of the txs must not be coinbases + for (var i = 1; i < this.txs.length; i++) { + if (this.txs[i].inputs.length === 1 && +this.txs[i].inputs[0].out.hash === 0) { + return false; + } + } + + // Build MerkleTree + this.merkleTree = this._buildMerkle(); + + // Check for duplicate tx ids + var unique = {}; + for (var i = 0; i < this.txs.length; i++) { + var hash = this.txs[i].hash('hex'); + if (unique[hash]) return false; + unique[hash] = true; + } + + // Check merkle root + return this.merkleTree[this.merkleTree.length-1] === this.merkleRoot; +}; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index db40ab22..88551ecc 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -17,6 +17,7 @@ function Chain(options) { this.prefix = 'bt/chain/'; this.storage = this.options.storage; this.strict = this.options.strict || false; + this.cacheLimit = this.options.cacheLimit || 2000; this.block = { list: [], @@ -256,12 +257,12 @@ Chain.prototype.add = function add(block) { }; Chain.prototype._compress = function compress() { - // Keep at least 1000 blocks and at most 2000 - if (this.block.list.length < 2000) + // Keep at least 1000 blocks and at most 2000 by default + if (this.block.list.length < this.cacheLimit) return; // Bloom filter rebuilt is needed - this.block.list = this.block.list.slice(-1000); + this.block.list = this.block.list.slice(-(this.cacheLimit / 2 | 0)); this.block.bloom.reset(); for (var i = 0; i < this.block.list.length; i++) diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index e6770e75..cc36a162 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -161,6 +161,19 @@ Peer.prototype.broadcast = function broadcast(items) { }; Peer.prototype.updateWatch = function updateWatch() { + if (!this.pool.options.relay) { + if (this.ack) { + var self = this; + if (this.pool.block.lastHash) + this.loadBlocks([ self.pool.block.lastHash ], 0); + else + this.pool.chain.getLast(function(hash) { + self.loadBlocks([ hash ], 0); + }); + } + return; + } + if (this.ack) this._write(this.framer.filterLoad(this.bloom, 'none')); }; @@ -417,6 +430,15 @@ Peer.prototype._handleInv = function handleInv(items) { }); this.emit('blocks', blocks); + if (!this.pool.options.relay) { + if (txs.length) + this.emit('txs', txs.map(function(tx) { + return tx.hash; + })); + this.getData(items); + return; + } + if (txs.length === 0) return; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 471eafe0..a21185c9 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -13,6 +13,7 @@ function Pool(options) { EventEmitter.call(this); this.options = options || {}; + this.options.relay = this.options.relay !== false; this.storage = this.options.storage; this.destroyed = false; this.size = options.size || 32; @@ -35,7 +36,12 @@ function Pool(options) { }; this.maxRetries = options.maxRetries || 42; this.requestTimeout = options.requestTimeout || 10000; - this.chain = new bcoin.chain({ storage: this.storage }); + this.chain = new bcoin.chain({ + storage: this.storage, + // Since regular blocks contain transactions and full merkle + // trees, it's risky to cache 2000 blocks. Let's do 100. + cacheLimit: !this.options.relay ? 100 : null + }); this.watchMap = {}; this.bloom = new bcoin.bloom(8 * 1024, 10, @@ -293,6 +299,12 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self.emit('block', block, peer); }); + if (!this.options.relay) { + peer.on('block', function(block) { + peer.emit('merkleblock', block); + }); + } + // Just FYI peer.on('reject', function(payload) { self.emit('reject', payload, peer); diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index a3be0e7b..0842b379 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -147,3 +147,9 @@ exports.hashType = { single: 3, anyonecaypay: 0x80 }; + +exports.block = { + maxSize: 1000000, + maxSigops: 1000000 / 50, + maxOrphanTx: 1000000 / 100 +}; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index d75b80c0..4970e645 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -128,7 +128,7 @@ function varint(arr, value, off) { arr[off + 4] = value >>> 24; return 5; } else { - p[off] = 0xff; + arr[off] = 0xff; utils.writeU64(arr, value, off + 1); return 9; } @@ -303,11 +303,11 @@ Framer.block = function _block(block, type) { assert.equal(off, 76); off += writeU32(p, block.nonce, off); - // txn_count (spec says this is a varint for some reason) assert.equal(off, 80); - off += writeU32(p, block.totalTX, off); if (type === 'merkleblock') { + // txn_count + off += writeU32(p, block.totalTX, off); // hash count assert.equal(off, 84); off += varint(p, block.hashes.length, off); @@ -323,6 +323,15 @@ Framer.block = function _block(block, type) { block.flags.forEach(function(flag) { p[off++] = flag; }); + } else if (type === 'block') { + // txn_count + off += varint(p, block.totalTX, off); + // txs + block.txs.forEach(function(tx) { + tx._raw.forEach(function(ch) { + p[off++] = ch; + }); + }); } return p; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 8238b436..4bceca0f 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -218,6 +218,17 @@ Parser.prototype.parseBlock = function parseBlock(p) { if (p.length < 84) return this._error('Invalid block size'); + var result = readIntv(p, 80); + var off = result.off; + var totalTX = result.r; + var txs = []; + + for (var i = 0; i < totalTX; i++) { + var tx = this.parseTX(p.slice(off)); + off += tx._off; + txs.push(tx); + } + return { version: readU32(p, 0), prevBlock: p.slice(4, 36), @@ -225,7 +236,8 @@ Parser.prototype.parseBlock = function parseBlock(p) { ts: readU32(p, 68), bits: readU32(p, 72), nonce: readU32(p, 76), - totalTX: readU32(p, 80) + totalTX: totalTX, + txs: txs }; }; @@ -316,7 +328,8 @@ Parser.prototype.parseTX = function parseTX(p) { version: readU32(p, 0), inputs: txIn, outputs: txOut, - lock: readU32(p, off) + lock: readU32(p, off), + _off: off + 4 }; };