From c033f5d465b94d0373b26454e57a01ae4bf5371c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 26 Nov 2016 06:08:35 -0800 Subject: [PATCH] coins: optimize. --- lib/blockchain/coins.js | 196 ++++++++++++++++++++++++---------------- lib/utils/encoding.js | 24 +++++ lib/utils/reader.js | 11 +++ 3 files changed, 152 insertions(+), 79 deletions(-) diff --git a/lib/blockchain/coins.js b/lib/blockchain/coins.js index c7212732..7352bdfc 100644 --- a/lib/blockchain/coins.js +++ b/lib/blockchain/coins.js @@ -10,6 +10,7 @@ var util = require('../utils/util'); var assert = require('assert'); var constants = require('../protocol/constants'); var Coin = require('../primitives/coin'); +var Output = require('../primitives/output'); var BufferReader = require('../utils/reader'); var BufferWriter = require('../utils/writer'); var compressor = require('./compress'); @@ -109,7 +110,7 @@ Coins.prototype.add = function add(coin) { return; } - this.outputs[coin.index] = coin; + this.outputs[coin.index] = CoinEntry.fromCoin(coin); }; /** @@ -119,6 +120,9 @@ Coins.prototype.add = function add(coin) { */ Coins.prototype.has = function has(index) { + if (index >= this.outputs.length) + return false; + return this.outputs[index] != null; }; @@ -129,31 +133,17 @@ Coins.prototype.has = function has(index) { */ Coins.prototype.get = function get(index) { - var coin = this.outputs[index]; + var coin; + + if (index >= this.outputs.length) + return null; + + coin = this.outputs[index]; + if (!coin) - return; + return null; - if (coin instanceof CompressedCoin) - coin = coin.toCoin(this, index); - - return coin; -}; - -/** - * Count unspent coins. - * @returns {Number} - */ - -Coins.prototype.count = function count(index) { - var total = 0; - var i; - - for (i = 0; i < this.outputs.length; i++) { - if (this.outputs[i]) - total++; - } - - return total; + return coin.toCoin(this, index); }; /** @@ -164,10 +154,12 @@ Coins.prototype.count = function count(index) { Coins.prototype.spend = function spend(index) { var coin = this.get(index); + if (!coin) return; this.outputs[index] = null; + return coin; }; @@ -178,10 +170,11 @@ Coins.prototype.spend = function spend(index) { Coins.prototype.size = function size() { var index = -1; - var i; + var i, output; for (i = this.outputs.length - 1; i >= 0; i--) { - if (this.outputs[i]) { + output = this.outputs[i]; + if (output) { index = i; break; } @@ -220,15 +213,15 @@ Coins.prototype.size = function size() { * @returns {Buffer} */ -Coins.prototype.toRaw = function toRaw(writer) { +Coins.prototype.toRaw = function toRaw() { var p = new BufferWriter(); var height = this.height; var length = this.size(); - var i, output, bits, fstart, flen, bit, oct; + var i, output, bits, start, len, bit, oct; // Return nothing if we're fully spent. if (length === 0) - return writer; + return null; // Varint version: hopefully some smartass // miner doesn't start mining `-1` versions. @@ -262,28 +255,19 @@ Coins.prototype.toRaw = function toRaw(writer) { // Fill the spent field with zeroes to avoid // allocating a buffer. We mark the spents // after rendering the final buffer. - flen = Math.ceil(length / 8); - p.writeVarint(flen); - fstart = p.written; - p.fill(0, flen); + len = Math.ceil(length / 8); + p.writeVarint(len); + start = p.written; + p.fill(0, len); + // Write the compressed outputs. for (i = 0; i < length; i++) { output = this.outputs[i]; if (!output) continue; - // If we read this coin from the db and - // didn't use it, it's still in its - // compressed form. Just write it back - // as a buffer for speed. - if (output instanceof CompressedCoin) { - p.writeBytes(output.toRaw()); - continue; - } - - compress.script(output.script, p); - p.writeVarint(output.value); + output.toRaw(p); } // Render the buffer with all @@ -293,16 +277,14 @@ Coins.prototype.toRaw = function toRaw(writer) { // Mark the spents in the spent field. // This is essentially a NOP for new coins. for (i = 0; i < length; i++) { - if (this.outputs[i]) + output = this.outputs[i]; + + if (output) continue; + bit = i % 8; oct = (i - bit) / 8; - p[fstart + oct] |= 1 << (7 - bit); - } - - if (writer) { - writer.writeBytes(p); - return writer; + p[start + oct] |= 1 << (7 - bit); } return p; @@ -318,7 +300,7 @@ Coins.prototype.toRaw = function toRaw(writer) { Coins.prototype.fromRaw = function fromRaw(data, hash, index) { var p = new BufferReader(data); var pos = 0; - var fstart, flen, bit, oct; + var start, len, bit, oct; var bits, coin, spent; this.version = p.readVarint(); @@ -334,15 +316,15 @@ Coins.prototype.fromRaw = function fromRaw(data, hash, index) { // Mark the start of the spent field and // seek past it to avoid reading a buffer. - flen = p.readVarint(); - fstart = p.offset; - p.seek(flen); + len = p.readVarint(); + start = p.offset; + p.seek(len); while (p.left()) { // Read a single bit out of the spent field. bit = pos % 8; oct = (pos - bit) / 8; - spent = (p.data[fstart + oct] >>> (7 - bit)) & 1; + spent = (data[start + oct] >>> (7 - bit)) & 1; // Already spent. if (spent) { @@ -353,7 +335,7 @@ Coins.prototype.fromRaw = function fromRaw(data, hash, index) { // Store the offset and size // in the compressed coin object. - coin = CompressedCoin.fromRaw(p); + coin = CoinEntry.fromRaw(p); this.outputs.push(coin); pos++; @@ -374,7 +356,7 @@ Coins.parseCoin = function parseCoin(data, hash, index) { var p = new BufferReader(data); var coin = new Coin(); var pos = 0; - var fstart, flen, bit, oct; + var start, len, bit, oct; var spent, bits; coin.version = p.readVarint(); @@ -392,20 +374,20 @@ Coins.parseCoin = function parseCoin(data, hash, index) { // Mark the start of the spent field and // seek past it to avoid reading a buffer. - flen = p.readVarint(); - fstart = p.offset; - p.seek(flen); + len = p.readVarint(); + start = p.offset; + p.seek(len); while (p.left()) { // Read a single bit out of the spent field. bit = pos % 8; oct = (pos - bit) / 8; - spent = (p.data[fstart + oct] >>> (7 - bit)) & 1; + spent = (data[start + oct] >>> (7 - bit)) & 1; // We found our coin. if (pos === index) { if (spent) - return; + return null; decompress.script(p, coin.script); coin.value = p.readVarint(); return coin; @@ -417,10 +399,12 @@ Coins.parseCoin = function parseCoin(data, hash, index) { continue; } - // Skip passed the compressed coin. + // Skip past the compressed coin. skipCoin(p); pos++; } + + return null; }; /** @@ -456,7 +440,7 @@ Coins.prototype.fromTX = function fromTX(tx) { continue; } - this.outputs.push(Coin.fromTX(tx, i)); + this.outputs.push(CoinEntry.fromTX(tx, i)); } return this; @@ -491,10 +475,11 @@ Coins.fromTX = function fromTX(tx) { * @param {Buffer} raw */ -function CompressedCoin(offset, size, raw) { - this.offset = offset; - this.size = size; - this.raw = raw; +function CoinEntry() { + this.offset = 0; + this.size = 0; + this.raw = null; + this.output = null; } /** @@ -504,9 +489,9 @@ function CompressedCoin(offset, size, raw) { * @returns {Coin} */ -CompressedCoin.prototype.toCoin = function toCoin(coins, index) { - var p = new BufferReader(this.raw); +CoinEntry.prototype.toCoin = function toCoin(coins, index) { var coin = new Coin(); + var p; // Load in all necessary properties // from the parent Coins object. @@ -516,6 +501,14 @@ CompressedCoin.prototype.toCoin = function toCoin(coins, index) { coin.hash = coins.hash; coin.index = index; + if (this.output) { + coin.script = this.output.script; + coin.value = this.output.value; + return coin; + } + + p = new BufferReader(this.raw); + // Seek to the coin's offset. p.seek(this.offset); @@ -532,20 +525,65 @@ CompressedCoin.prototype.toCoin = function toCoin(coins, index) { * @returns {Buffer} */ -CompressedCoin.prototype.toRaw = function toRaw() { - return this.raw.slice(this.offset, this.offset + this.size); +CoinEntry.prototype.toRaw = function toRaw(p) { + var raw; + + if (this.output) { + compress.script(this.output.script, p); + p.writeVarint(this.output.value); + return; + } + + assert(this.raw); + + // If we read this coin from the db and + // didn't use it, it's still in its + // compressed form. Just write it back + // as a buffer for speed. + raw = this.raw.slice(this.offset, this.offset + this.size); + + p.writeBytes(raw); }; /** * Instantiate compressed coin from reader. * @param {BufferReader} p - * @returns {CompressedCoin} + * @returns {CoinEntry} */ -CompressedCoin.fromRaw = function fromRaw(p) { - var offset = p.offset; - var size = skipCoin(p); - return new CompressedCoin(offset, size, p.data); +CoinEntry.fromRaw = function fromRaw(p) { + var coin = new CoinEntry(); + coin.offset = p.offset; + coin.size = skipCoin(p); + coin.raw = p.data; + return coin; +}; + +/** + * Instantiate compressed coin from tx. + * @param {TX} tx + * @param {Number} index + * @returns {CoinEntry} + */ + +CoinEntry.fromTX = function fromTX(tx, index) { + var coin = new CoinEntry(); + coin.output = tx.outputs[index]; + return coin; +}; + +/** + * Instantiate compressed coin from coin. + * @param {Coin} coin + * @returns {CoinEntry} + */ + +CoinEntry.fromCoin = function fromCoin(coin) { + var entry = new CoinEntry(); + entry.output = new Output(); + entry.output.script = coin.script; + entry.output.value = coin.value; + return entry; }; /* @@ -572,7 +610,7 @@ function skipCoin(p) { } // Skip past the value. - p.readVarint(); + p.skipVarint(); return p.offset - start; } diff --git a/lib/utils/encoding.js b/lib/utils/encoding.js index bf6dca23..0a3afb6e 100644 --- a/lib/utils/encoding.js +++ b/lib/utils/encoding.js @@ -423,6 +423,30 @@ encoding.readVarint = function readVarint(data, off, big) { return { size: size, value: value }; }; +/** + * Read a varint size. + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + */ + +encoding.skipVarint = function skipVarint(data, off) { + off = off >>> 0; + + assert(off < data.length); + + switch (data[off]) { + case 0xff: + return 9; + case 0xfe: + return 5; + case 0xfd: + return 3; + default: + return 1; + } +}; + /** * Write a varint. * @param {Buffer} dst diff --git a/lib/utils/reader.js b/lib/utils/reader.js index 54595746..6974aa78 100644 --- a/lib/utils/reader.js +++ b/lib/utils/reader.js @@ -476,6 +476,17 @@ BufferReader.prototype.readVarint = function readVarint(big) { return result.value; }; +/** + * Skip past a varint. + * @returns {Number} + */ + +BufferReader.prototype.skipVarint = function skipVarint() { + var size = encoding.skipVarint(this.data, this.offset); + assert(this.offset + size <= this.data.length); + this.offset += size; +}; + /** * Read a varint (type 2). * @param {Boolean?} big - Whether to read as a big number.