coins: optimize.

This commit is contained in:
Christopher Jeffrey 2016-11-26 06:08:35 -08:00
parent 9bc92abb41
commit c033f5d465
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 152 additions and 79 deletions

View File

@ -10,6 +10,7 @@ var util = require('../utils/util');
var assert = require('assert'); var assert = require('assert');
var constants = require('../protocol/constants'); var constants = require('../protocol/constants');
var Coin = require('../primitives/coin'); var Coin = require('../primitives/coin');
var Output = require('../primitives/output');
var BufferReader = require('../utils/reader'); var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer'); var BufferWriter = require('../utils/writer');
var compressor = require('./compress'); var compressor = require('./compress');
@ -109,7 +110,7 @@ Coins.prototype.add = function add(coin) {
return; 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) { Coins.prototype.has = function has(index) {
if (index >= this.outputs.length)
return false;
return this.outputs[index] != null; return this.outputs[index] != null;
}; };
@ -129,31 +133,17 @@ Coins.prototype.has = function has(index) {
*/ */
Coins.prototype.get = function get(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) if (!coin)
return; return null;
if (coin instanceof CompressedCoin) return coin.toCoin(this, index);
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;
}; };
/** /**
@ -164,10 +154,12 @@ Coins.prototype.count = function count(index) {
Coins.prototype.spend = function spend(index) { Coins.prototype.spend = function spend(index) {
var coin = this.get(index); var coin = this.get(index);
if (!coin) if (!coin)
return; return;
this.outputs[index] = null; this.outputs[index] = null;
return coin; return coin;
}; };
@ -178,10 +170,11 @@ Coins.prototype.spend = function spend(index) {
Coins.prototype.size = function size() { Coins.prototype.size = function size() {
var index = -1; var index = -1;
var i; var i, output;
for (i = this.outputs.length - 1; i >= 0; i--) { for (i = this.outputs.length - 1; i >= 0; i--) {
if (this.outputs[i]) { output = this.outputs[i];
if (output) {
index = i; index = i;
break; break;
} }
@ -220,15 +213,15 @@ Coins.prototype.size = function size() {
* @returns {Buffer} * @returns {Buffer}
*/ */
Coins.prototype.toRaw = function toRaw(writer) { Coins.prototype.toRaw = function toRaw() {
var p = new BufferWriter(); var p = new BufferWriter();
var height = this.height; var height = this.height;
var length = this.size(); 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. // Return nothing if we're fully spent.
if (length === 0) if (length === 0)
return writer; return null;
// Varint version: hopefully some smartass // Varint version: hopefully some smartass
// miner doesn't start mining `-1` versions. // 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 // Fill the spent field with zeroes to avoid
// allocating a buffer. We mark the spents // allocating a buffer. We mark the spents
// after rendering the final buffer. // after rendering the final buffer.
flen = Math.ceil(length / 8); len = Math.ceil(length / 8);
p.writeVarint(flen); p.writeVarint(len);
fstart = p.written; start = p.written;
p.fill(0, flen); p.fill(0, len);
// Write the compressed outputs.
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
output = this.outputs[i]; output = this.outputs[i];
if (!output) if (!output)
continue; continue;
// If we read this coin from the db and output.toRaw(p);
// 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);
} }
// Render the buffer with all // Render the buffer with all
@ -293,16 +277,14 @@ Coins.prototype.toRaw = function toRaw(writer) {
// Mark the spents in the spent field. // Mark the spents in the spent field.
// This is essentially a NOP for new coins. // This is essentially a NOP for new coins.
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
if (this.outputs[i]) output = this.outputs[i];
if (output)
continue; continue;
bit = i % 8; bit = i % 8;
oct = (i - bit) / 8; oct = (i - bit) / 8;
p[fstart + oct] |= 1 << (7 - bit); p[start + oct] |= 1 << (7 - bit);
}
if (writer) {
writer.writeBytes(p);
return writer;
} }
return p; return p;
@ -318,7 +300,7 @@ Coins.prototype.toRaw = function toRaw(writer) {
Coins.prototype.fromRaw = function fromRaw(data, hash, index) { Coins.prototype.fromRaw = function fromRaw(data, hash, index) {
var p = new BufferReader(data); var p = new BufferReader(data);
var pos = 0; var pos = 0;
var fstart, flen, bit, oct; var start, len, bit, oct;
var bits, coin, spent; var bits, coin, spent;
this.version = p.readVarint(); this.version = p.readVarint();
@ -334,15 +316,15 @@ Coins.prototype.fromRaw = function fromRaw(data, hash, index) {
// Mark the start of the spent field and // Mark the start of the spent field and
// seek past it to avoid reading a buffer. // seek past it to avoid reading a buffer.
flen = p.readVarint(); len = p.readVarint();
fstart = p.offset; start = p.offset;
p.seek(flen); p.seek(len);
while (p.left()) { while (p.left()) {
// Read a single bit out of the spent field. // Read a single bit out of the spent field.
bit = pos % 8; bit = pos % 8;
oct = (pos - bit) / 8; oct = (pos - bit) / 8;
spent = (p.data[fstart + oct] >>> (7 - bit)) & 1; spent = (data[start + oct] >>> (7 - bit)) & 1;
// Already spent. // Already spent.
if (spent) { if (spent) {
@ -353,7 +335,7 @@ Coins.prototype.fromRaw = function fromRaw(data, hash, index) {
// Store the offset and size // Store the offset and size
// in the compressed coin object. // in the compressed coin object.
coin = CompressedCoin.fromRaw(p); coin = CoinEntry.fromRaw(p);
this.outputs.push(coin); this.outputs.push(coin);
pos++; pos++;
@ -374,7 +356,7 @@ Coins.parseCoin = function parseCoin(data, hash, index) {
var p = new BufferReader(data); var p = new BufferReader(data);
var coin = new Coin(); var coin = new Coin();
var pos = 0; var pos = 0;
var fstart, flen, bit, oct; var start, len, bit, oct;
var spent, bits; var spent, bits;
coin.version = p.readVarint(); coin.version = p.readVarint();
@ -392,20 +374,20 @@ Coins.parseCoin = function parseCoin(data, hash, index) {
// Mark the start of the spent field and // Mark the start of the spent field and
// seek past it to avoid reading a buffer. // seek past it to avoid reading a buffer.
flen = p.readVarint(); len = p.readVarint();
fstart = p.offset; start = p.offset;
p.seek(flen); p.seek(len);
while (p.left()) { while (p.left()) {
// Read a single bit out of the spent field. // Read a single bit out of the spent field.
bit = pos % 8; bit = pos % 8;
oct = (pos - bit) / 8; oct = (pos - bit) / 8;
spent = (p.data[fstart + oct] >>> (7 - bit)) & 1; spent = (data[start + oct] >>> (7 - bit)) & 1;
// We found our coin. // We found our coin.
if (pos === index) { if (pos === index) {
if (spent) if (spent)
return; return null;
decompress.script(p, coin.script); decompress.script(p, coin.script);
coin.value = p.readVarint(); coin.value = p.readVarint();
return coin; return coin;
@ -417,10 +399,12 @@ Coins.parseCoin = function parseCoin(data, hash, index) {
continue; continue;
} }
// Skip passed the compressed coin. // Skip past the compressed coin.
skipCoin(p); skipCoin(p);
pos++; pos++;
} }
return null;
}; };
/** /**
@ -456,7 +440,7 @@ Coins.prototype.fromTX = function fromTX(tx) {
continue; continue;
} }
this.outputs.push(Coin.fromTX(tx, i)); this.outputs.push(CoinEntry.fromTX(tx, i));
} }
return this; return this;
@ -491,10 +475,11 @@ Coins.fromTX = function fromTX(tx) {
* @param {Buffer} raw * @param {Buffer} raw
*/ */
function CompressedCoin(offset, size, raw) { function CoinEntry() {
this.offset = offset; this.offset = 0;
this.size = size; this.size = 0;
this.raw = raw; this.raw = null;
this.output = null;
} }
/** /**
@ -504,9 +489,9 @@ function CompressedCoin(offset, size, raw) {
* @returns {Coin} * @returns {Coin}
*/ */
CompressedCoin.prototype.toCoin = function toCoin(coins, index) { CoinEntry.prototype.toCoin = function toCoin(coins, index) {
var p = new BufferReader(this.raw);
var coin = new Coin(); var coin = new Coin();
var p;
// Load in all necessary properties // Load in all necessary properties
// from the parent Coins object. // from the parent Coins object.
@ -516,6 +501,14 @@ CompressedCoin.prototype.toCoin = function toCoin(coins, index) {
coin.hash = coins.hash; coin.hash = coins.hash;
coin.index = index; 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. // Seek to the coin's offset.
p.seek(this.offset); p.seek(this.offset);
@ -532,20 +525,65 @@ CompressedCoin.prototype.toCoin = function toCoin(coins, index) {
* @returns {Buffer} * @returns {Buffer}
*/ */
CompressedCoin.prototype.toRaw = function toRaw() { CoinEntry.prototype.toRaw = function toRaw(p) {
return this.raw.slice(this.offset, this.offset + this.size); 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. * Instantiate compressed coin from reader.
* @param {BufferReader} p * @param {BufferReader} p
* @returns {CompressedCoin} * @returns {CoinEntry}
*/ */
CompressedCoin.fromRaw = function fromRaw(p) { CoinEntry.fromRaw = function fromRaw(p) {
var offset = p.offset; var coin = new CoinEntry();
var size = skipCoin(p); coin.offset = p.offset;
return new CompressedCoin(offset, size, p.data); 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. // Skip past the value.
p.readVarint(); p.skipVarint();
return p.offset - start; return p.offset - start;
} }

View File

@ -423,6 +423,30 @@ encoding.readVarint = function readVarint(data, off, big) {
return { size: size, value: value }; 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. * Write a varint.
* @param {Buffer} dst * @param {Buffer} dst

View File

@ -476,6 +476,17 @@ BufferReader.prototype.readVarint = function readVarint(big) {
return result.value; 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). * Read a varint (type 2).
* @param {Boolean?} big - Whether to read as a big number. * @param {Boolean?} big - Whether to read as a big number.