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 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;
}

View File

@ -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

View File

@ -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.