chain: new coins compression.
This commit is contained in:
parent
923364a70a
commit
63c42bf390
@ -15,28 +15,30 @@ wtx = TX.fromRaw(wtx.trim(), 'hex');
|
|||||||
|
|
||||||
var coins = Coins.fromTX(wtx);
|
var coins = Coins.fromTX(wtx);
|
||||||
var raw;
|
var raw;
|
||||||
|
//raw = coins.toRaw2();
|
||||||
|
//console.log(Coins.fromRaw2(raw));
|
||||||
|
|
||||||
var end = bench('serialize');
|
var end = bench('serialize');
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
raw = coins.toRaw();
|
raw = coins.toRaw2();
|
||||||
end(i);
|
end(i);
|
||||||
|
|
||||||
var end = bench('parse');
|
var end = bench('parse');
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
Coins.fromRaw(raw);
|
Coins.fromRaw2(raw);
|
||||||
end(i);
|
end(i);
|
||||||
|
|
||||||
var end = bench('parse-single');
|
var end = bench('parse-single');
|
||||||
var hash = wtx.hash('hex');
|
var hash = wtx.hash('hex');
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
Coins.parseCoin(raw, hash, 5);
|
Coins.parseCoin2(raw, hash, 5);
|
||||||
end(i);
|
end(i);
|
||||||
|
|
||||||
var coins = Coins.fromRaw(raw);
|
var coins = Coins.fromRaw2(raw);
|
||||||
var end = bench('get');
|
var end = bench('get');
|
||||||
var j;
|
var j;
|
||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
for (var j = 0; j < coins.outputs.length; j++)
|
for (var j = 0; j < coins.outputs.length; j++)
|
||||||
coins.get(j);
|
coins.get2(j);
|
||||||
end(i * coins.outputs.length);
|
end(i * coins.outputs.length);
|
||||||
|
|||||||
@ -639,7 +639,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
|
|||||||
|
|
||||||
// Ensure tx is not double spending an output.
|
// Ensure tx is not double spending an output.
|
||||||
if (!tx.isCoinbase()) {
|
if (!tx.isCoinbase()) {
|
||||||
if (!view.fillCoins(tx)) {
|
if (!view.fillCoins2(tx)) {
|
||||||
assert(!historical, 'BUG: Spent inputs in historical data!');
|
assert(!historical, 'BUG: Spent inputs in historical data!');
|
||||||
throw new VerifyError(block,
|
throw new VerifyError(block,
|
||||||
'invalid',
|
'invalid',
|
||||||
|
|||||||
@ -633,7 +633,7 @@ ChainDB.prototype.getCoin = co(function* getCoin(hash, index) {
|
|||||||
coins = this.coinCache.get(hash);
|
coins = this.coinCache.get(hash);
|
||||||
|
|
||||||
if (coins)
|
if (coins)
|
||||||
return Coins.parseCoin(coins, hash, index);
|
return Coins.parseCoin2(coins, hash, index);
|
||||||
|
|
||||||
coins = yield this.db.get(layout.c(hash));
|
coins = yield this.db.get(layout.c(hash));
|
||||||
|
|
||||||
@ -643,7 +643,7 @@ ChainDB.prototype.getCoin = co(function* getCoin(hash, index) {
|
|||||||
if (state === this.state)
|
if (state === this.state)
|
||||||
this.coinCache.set(hash, coins);
|
this.coinCache.set(hash, coins);
|
||||||
|
|
||||||
return Coins.parseCoin(coins, hash, index);
|
return Coins.parseCoin2(coins, hash, index);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -662,7 +662,7 @@ ChainDB.prototype.getCoins = co(function* getCoins(hash) {
|
|||||||
coins = this.coinCache.get(hash);
|
coins = this.coinCache.get(hash);
|
||||||
|
|
||||||
if (coins)
|
if (coins)
|
||||||
return Coins.fromRaw(coins, hash);
|
return Coins.fromRaw2(coins, hash);
|
||||||
|
|
||||||
coins = yield this.db.get(layout.c(hash));
|
coins = yield this.db.get(layout.c(hash));
|
||||||
|
|
||||||
@ -672,7 +672,7 @@ ChainDB.prototype.getCoins = co(function* getCoins(hash) {
|
|||||||
if (state === this.state)
|
if (state === this.state)
|
||||||
this.coinCache.set(hash, coins);
|
this.coinCache.set(hash, coins);
|
||||||
|
|
||||||
return Coins.fromRaw(coins, hash);
|
return Coins.fromRaw2(coins, hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1672,7 +1672,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
|
|||||||
|
|
||||||
for (i = 0; i < view.length; i++) {
|
for (i = 0; i < view.length; i++) {
|
||||||
coins = view[i];
|
coins = view[i];
|
||||||
raw = coins.toRaw();
|
raw = coins.toRaw2();
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
this.del(layout.c(coins.hash));
|
this.del(layout.c(coins.hash));
|
||||||
this.coinCache.unpush(coins.hash);
|
this.coinCache.unpush(coins.hash);
|
||||||
@ -1768,7 +1768,7 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
|
|||||||
|
|
||||||
for (i = 0; i < view.length; i++) {
|
for (i = 0; i < view.length; i++) {
|
||||||
coins = view[i];
|
coins = view[i];
|
||||||
raw = coins.toRaw();
|
raw = coins.toRaw2();
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
this.del(layout.c(coins.hash));
|
this.del(layout.c(coins.hash));
|
||||||
this.coinCache.unpush(coins.hash);
|
this.coinCache.unpush(coins.hash);
|
||||||
|
|||||||
@ -102,14 +102,12 @@ Coins.prototype.add = function add(coin) {
|
|||||||
this.coinbase = coin.coinbase;
|
this.coinbase = coin.coinbase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (coin.script.isUnspendable())
|
||||||
|
return;
|
||||||
|
|
||||||
while (this.outputs.length <= coin.index)
|
while (this.outputs.length <= coin.index)
|
||||||
this.outputs.push(null);
|
this.outputs.push(null);
|
||||||
|
|
||||||
if (coin.script.isUnspendable()) {
|
|
||||||
this.outputs[coin.index] = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outputs[coin.index] = CoinEntry.fromCoin(coin);
|
this.outputs[coin.index] = CoinEntry.fromCoin(coin);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,10 +157,62 @@ Coins.prototype.spend = function spend(index) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
this.outputs[index] = null;
|
this.outputs[index] = null;
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
return coin;
|
return coin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a coin.
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.prototype.get2 = function get2(index) {
|
||||||
|
var coin;
|
||||||
|
|
||||||
|
if (index >= this.outputs.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
coin = this.outputs[index];
|
||||||
|
|
||||||
|
if (!coin)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return coin.toCoin2(this, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a coin and return it.
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.prototype.spend2 = function spend2(index) {
|
||||||
|
var coin = this.get2(index);
|
||||||
|
|
||||||
|
if (!coin)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.outputs[index] = null;
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
|
return coin;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup spent outputs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.prototype.cleanup = function cleanup() {
|
||||||
|
var len = this.outputs.length;
|
||||||
|
|
||||||
|
while (len > 0 && !this.outputs[len - 1])
|
||||||
|
len--;
|
||||||
|
|
||||||
|
this.outputs.length = len;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count up to the last available index.
|
* Count up to the last available index.
|
||||||
* @returns {Number}
|
* @returns {Number}
|
||||||
@ -294,7 +344,7 @@ Coins.prototype.toRaw = function toRaw() {
|
|||||||
* @returns {Object} A "naked" coins object.
|
* @returns {Object} A "naked" coins object.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Coins.prototype.fromRaw = function fromRaw(data, hash, index) {
|
Coins.prototype.fromRaw = function fromRaw(data, hash) {
|
||||||
var br = new BufferReader(data);
|
var br = new BufferReader(data);
|
||||||
var pos = 0;
|
var pos = 0;
|
||||||
var bits, len, start, bit, oct, spent, coin;
|
var bits, len, start, bit, oct, spent, coin;
|
||||||
@ -395,7 +445,7 @@ Coins.parseCoin = function parseCoin(data, hash, index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip past the compressed coin.
|
// Skip past the compressed coin.
|
||||||
skipCoin(br);
|
decompress.skip(br);
|
||||||
pos++;
|
pos++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -411,6 +461,227 @@ Coins.fromRaw = function fromRaw(data, hash) {
|
|||||||
return new Coins().fromRaw(data, hash);
|
return new Coins().fromRaw(data, hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the coins object.
|
||||||
|
* @param {TX|Coins} tx
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.prototype.toRaw2 = function toRaw2() {
|
||||||
|
var bw = new BufferWriter();
|
||||||
|
var len = this.outputs.length;
|
||||||
|
var first = len > 0 && this.outputs[0];
|
||||||
|
var second = len > 1 && this.outputs[1];
|
||||||
|
var size = 0;
|
||||||
|
var nonzero = 0;
|
||||||
|
var i, j, code, ch, output;
|
||||||
|
|
||||||
|
// Return nothing if we're fully spent.
|
||||||
|
if (len === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Calculate number of unspents and spent field size.
|
||||||
|
// size = number of bytes required for the bit field.
|
||||||
|
// nonzero = number of non-zero bytes required.
|
||||||
|
for (i = 0; 2 + i * 8 < len; i++) {
|
||||||
|
for (j = 0; j < 8 && 2 + i * 8 + j < len; j++) {
|
||||||
|
if (this.outputs[2 + i * 8 + j]) {
|
||||||
|
size = i + 1;
|
||||||
|
nonzero++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first && !second)
|
||||||
|
nonzero -= 1;
|
||||||
|
|
||||||
|
// Calculate header code.
|
||||||
|
code = 8 * nonzero;
|
||||||
|
|
||||||
|
if (this.coinbase)
|
||||||
|
code += 1;
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
code += 2;
|
||||||
|
|
||||||
|
if (second)
|
||||||
|
code += 4;
|
||||||
|
|
||||||
|
// Write headers.
|
||||||
|
bw.writeVarint(this.version);
|
||||||
|
bw.writeU32(this.height);
|
||||||
|
bw.writeVarint(code);
|
||||||
|
|
||||||
|
// Write the spent field.
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
ch = 0;
|
||||||
|
for (j = 0; j < 8 && 2 + i * 8 + j < len; j++) {
|
||||||
|
if (this.outputs[2 + i * 8 + j])
|
||||||
|
ch |= 1 << j;
|
||||||
|
}
|
||||||
|
bw.writeU8(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the compressed outputs.
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
output = this.outputs[i];
|
||||||
|
|
||||||
|
if (!output)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
output.toWriter2(bw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bw.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse serialized coins.
|
||||||
|
* @param {Buffer} data
|
||||||
|
* @param {Hash} hash
|
||||||
|
* @returns {Object} A "naked" coins object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.prototype.fromRaw2 = function fromRaw2(data, hash) {
|
||||||
|
var br = new BufferReader(data);
|
||||||
|
var i, code, field, nonzero, ch, unspent, coin;
|
||||||
|
|
||||||
|
this.hash = hash;
|
||||||
|
|
||||||
|
// Read headers.
|
||||||
|
this.version = br.readVarint();
|
||||||
|
this.height = br.readU32();
|
||||||
|
code = br.readVarint();
|
||||||
|
this.coinbase = (code & 1) !== 0;
|
||||||
|
|
||||||
|
// Setup spent field.
|
||||||
|
field = [
|
||||||
|
(code & 2) !== 0,
|
||||||
|
(code & 4) !== 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// Recalculate number of non-zero bytes.
|
||||||
|
nonzero = code / 8 | 0;
|
||||||
|
|
||||||
|
if ((code & 6) === 0)
|
||||||
|
nonzero += 1;
|
||||||
|
|
||||||
|
// Read spent field.
|
||||||
|
while (nonzero > 0) {
|
||||||
|
ch = br.readU8();
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
unspent = (ch & (1 << i)) !== 0;
|
||||||
|
field.push(unspent);
|
||||||
|
}
|
||||||
|
if (ch !== 0)
|
||||||
|
nonzero--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read outputs.
|
||||||
|
for (i = 0; i < field.length; i++) {
|
||||||
|
if (!field[i]) {
|
||||||
|
this.outputs.push(null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the offset and size
|
||||||
|
// in the compressed coin object.
|
||||||
|
coin = CoinEntry.fromReader2(br);
|
||||||
|
|
||||||
|
this.outputs.push(coin);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a single serialized coin.
|
||||||
|
* @param {Buffer} data
|
||||||
|
* @param {Hash} hash
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.parseCoin2 = function parseCoin2(data, hash, index) {
|
||||||
|
var br = new BufferReader(data);
|
||||||
|
var coin = new Coin();
|
||||||
|
var i, code, field, nonzero, ch, unspent;
|
||||||
|
|
||||||
|
coin.hash = hash;
|
||||||
|
coin.index = index;
|
||||||
|
|
||||||
|
// Read headers.
|
||||||
|
coin.version = br.readVarint();
|
||||||
|
coin.height = br.readU32();
|
||||||
|
code = br.readVarint();
|
||||||
|
coin.coinbase = (code & 1) !== 0;
|
||||||
|
|
||||||
|
// Setup spent field.
|
||||||
|
field = [
|
||||||
|
(code & 2) !== 0,
|
||||||
|
(code & 4) !== 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// Recalculate number of non-zero bytes.
|
||||||
|
nonzero = code / 8 | 0;
|
||||||
|
|
||||||
|
if ((code & 6) === 0)
|
||||||
|
nonzero += 1;
|
||||||
|
|
||||||
|
// Read spent field.
|
||||||
|
while (nonzero > 0 && field.length <= index) {
|
||||||
|
ch = br.readU8();
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
unspent = (ch & (1 << i)) !== 0;
|
||||||
|
field.push(unspent);
|
||||||
|
}
|
||||||
|
if (ch !== 0)
|
||||||
|
nonzero--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.length <= index)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (nonzero > 0) {
|
||||||
|
if (br.readU8() !== 0)
|
||||||
|
nonzero--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read outputs.
|
||||||
|
for (i = 0; i < field.length; i++) {
|
||||||
|
if (i === index) {
|
||||||
|
if (!field[i])
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Read compressed output.
|
||||||
|
decompress.output2(coin, br);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!field[i])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
decompress.skip2(br);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coin;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate coins from a serialized Buffer.
|
||||||
|
* @param {Buffer} data
|
||||||
|
* @param {Hash} hash - Transaction hash.
|
||||||
|
* @returns {Coins}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Coins.fromRaw2 = function fromRaw2(data, hash) {
|
||||||
|
return new Coins().fromRaw2(data, hash);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject properties from tx.
|
* Inject properties from tx.
|
||||||
* @private
|
* @private
|
||||||
@ -436,6 +707,8 @@ Coins.prototype.fromTX = function fromTX(tx) {
|
|||||||
this.outputs.push(CoinEntry.fromTX(tx, i));
|
this.outputs.push(CoinEntry.fromTX(tx, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -505,9 +778,42 @@ CoinEntry.prototype.toCoin = function toCoin(coins, index) {
|
|||||||
// Seek to the coin's offset.
|
// Seek to the coin's offset.
|
||||||
br.seek(this.offset);
|
br.seek(this.offset);
|
||||||
|
|
||||||
decompress.script(coin.script, br);
|
decompress.output(coin, br);
|
||||||
|
|
||||||
coin.value = br.readVarint();
|
return coin;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the deferred data and return a Coin.
|
||||||
|
* @param {Coins} coins
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinEntry.prototype.toCoin2 = function toCoin2(coins, index) {
|
||||||
|
var coin = new Coin();
|
||||||
|
var br;
|
||||||
|
|
||||||
|
// Load in all necessary properties
|
||||||
|
// from the parent Coins object.
|
||||||
|
coin.version = coins.version;
|
||||||
|
coin.coinbase = coins.coinbase;
|
||||||
|
coin.height = coins.height;
|
||||||
|
coin.hash = coins.hash;
|
||||||
|
coin.index = index;
|
||||||
|
|
||||||
|
if (this.output) {
|
||||||
|
coin.script = this.output.script;
|
||||||
|
coin.value = this.output.value;
|
||||||
|
return coin;
|
||||||
|
}
|
||||||
|
|
||||||
|
br = new BufferReader(this.raw);
|
||||||
|
|
||||||
|
// Seek to the coin's offset.
|
||||||
|
br.seek(this.offset);
|
||||||
|
|
||||||
|
decompress.output2(coin, br);
|
||||||
|
|
||||||
return coin;
|
return coin;
|
||||||
};
|
};
|
||||||
@ -521,8 +827,31 @@ CoinEntry.prototype.toWriter = function toWriter(bw) {
|
|||||||
var raw;
|
var raw;
|
||||||
|
|
||||||
if (this.output) {
|
if (this.output) {
|
||||||
compress.script(this.output.script, bw);
|
compress.output(this.output, bw);
|
||||||
bw.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);
|
||||||
|
|
||||||
|
bw.writeBytes(raw);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice off the part of the buffer
|
||||||
|
* relevant to this particular coin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinEntry.prototype.toWriter2 = function toWriter2(bw) {
|
||||||
|
var raw;
|
||||||
|
|
||||||
|
if (this.output) {
|
||||||
|
compress.output2(this.output, bw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,7 +875,21 @@ CoinEntry.prototype.toWriter = function toWriter(bw) {
|
|||||||
CoinEntry.fromReader = function fromReader(br) {
|
CoinEntry.fromReader = function fromReader(br) {
|
||||||
var entry = new CoinEntry();
|
var entry = new CoinEntry();
|
||||||
entry.offset = br.offset;
|
entry.offset = br.offset;
|
||||||
entry.size = skipCoin(br);
|
entry.size = decompress.skip(br);
|
||||||
|
entry.raw = br.data;
|
||||||
|
return entry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate compressed coin from reader.
|
||||||
|
* @param {BufferReader} br
|
||||||
|
* @returns {CoinEntry}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinEntry.fromReader2 = function fromReader2(br) {
|
||||||
|
var entry = new CoinEntry();
|
||||||
|
entry.offset = br.offset;
|
||||||
|
entry.size = decompress.skip2(br);
|
||||||
entry.raw = br.data;
|
entry.raw = br.data;
|
||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
@ -578,35 +921,6 @@ CoinEntry.fromCoin = function fromCoin(coin) {
|
|||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Helpers
|
|
||||||
*/
|
|
||||||
|
|
||||||
function skipCoin(br) {
|
|
||||||
var start = br.offset;
|
|
||||||
|
|
||||||
// Skip past the compressed scripts.
|
|
||||||
switch (br.readU8()) {
|
|
||||||
case 0:
|
|
||||||
br.seek(br.readVarint());
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
br.seek(20);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
br.seek(33);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Bad prefix.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip past the value.
|
|
||||||
br.skipVarint();
|
|
||||||
|
|
||||||
return br.offset - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -70,6 +70,22 @@ CoinView.prototype.get = function get(hash, index) {
|
|||||||
return coins.get(index);
|
return coins.get(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a coin.
|
||||||
|
* @param {Hash} hash
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinView.prototype.get2 = function get2(hash, index) {
|
||||||
|
var coins = this.coins[hash];
|
||||||
|
|
||||||
|
if (!coins)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return coins.get2(index);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether the collection has a coin.
|
* Test whether the collection has a coin.
|
||||||
* @param {Hash} hash
|
* @param {Hash} hash
|
||||||
@ -102,6 +118,22 @@ CoinView.prototype.spend = function spend(hash, index) {
|
|||||||
return coins.spend(index);
|
return coins.spend(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a coin and return it.
|
||||||
|
* @param {Hash} hash
|
||||||
|
* @param {Number} index
|
||||||
|
* @returns {Coin}
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinView.prototype.spend2 = function spend2(hash, index) {
|
||||||
|
var coins = this.coins[hash];
|
||||||
|
|
||||||
|
if (!coins)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return coins.spend2(index);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill transaction(s) with coins.
|
* Fill transaction(s) with coins.
|
||||||
* @param {TX} tx
|
* @param {TX} tx
|
||||||
@ -122,6 +154,26 @@ CoinView.prototype.fillCoins = function fillCoins(tx) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill transaction(s) with coins.
|
||||||
|
* @param {TX} tx
|
||||||
|
* @returns {Boolean} True if all inputs were filled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
CoinView.prototype.fillCoins2 = function fillCoins2(tx) {
|
||||||
|
var i, input, prevout;
|
||||||
|
|
||||||
|
for (i = 0; i < tx.inputs.length; i++) {
|
||||||
|
input = tx.inputs[i];
|
||||||
|
prevout = input.prevout;
|
||||||
|
input.coin = this.spend2(prevout.hash, prevout.index);
|
||||||
|
if (!input.coin)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert collection to an array.
|
* Convert collection to an array.
|
||||||
* @returns {Coins[]}
|
* @returns {Coins[]}
|
||||||
|
|||||||
@ -103,6 +103,218 @@ function decompressScript(script, br) {
|
|||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress a script, write directly to the buffer.
|
||||||
|
* @param {Script} script
|
||||||
|
* @param {BufferWriter} bw
|
||||||
|
*/
|
||||||
|
|
||||||
|
function compressScript2(script, bw) {
|
||||||
|
var data;
|
||||||
|
|
||||||
|
// Attempt to compress the output scripts.
|
||||||
|
// We can _only_ ever compress them if
|
||||||
|
// they are serialized as minimaldata, as
|
||||||
|
// we need to recreate them when we read
|
||||||
|
// them.
|
||||||
|
|
||||||
|
// P2PKH -> 0 | key-hash
|
||||||
|
// Saves 5 bytes.
|
||||||
|
if (script.isPubkeyhash(true)) {
|
||||||
|
data = script.code[2].data;
|
||||||
|
bw.writeU8(0);
|
||||||
|
bw.writeBytes(data);
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2SH -> 1 | script-hash
|
||||||
|
// Saves 3 bytes.
|
||||||
|
if (script.isScripthash()) {
|
||||||
|
data = script.code[1].data;
|
||||||
|
bw.writeU8(1);
|
||||||
|
bw.writeBytes(data);
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// P2PK -> 2-5 | compressed-key
|
||||||
|
// Only works if the key is valid.
|
||||||
|
// Saves up to 35 bytes.
|
||||||
|
if (script.isPubkey(true)) {
|
||||||
|
data = script.code[0].data;
|
||||||
|
if (publicKeyVerify(data)) {
|
||||||
|
data = compressKey2(data);
|
||||||
|
bw.writeBytes(data);
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw -> varlen + 6 | script
|
||||||
|
bw.writeVarint(script.raw.length + 6);
|
||||||
|
bw.writeBytes(script.raw);
|
||||||
|
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a script from buffer reader.
|
||||||
|
* @param {Script} script
|
||||||
|
* @param {BufferReader} br
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decompressScript2(script, br) {
|
||||||
|
var size, data;
|
||||||
|
|
||||||
|
// Decompress the script.
|
||||||
|
switch (br.readU8()) {
|
||||||
|
case 0:
|
||||||
|
data = br.readBytes(20, true);
|
||||||
|
script.fromPubkeyhash(data);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
data = br.readBytes(20, true);
|
||||||
|
script.fromScripthash(data);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
br.offset -= 1;
|
||||||
|
data = br.readBytes(33, true);
|
||||||
|
// Decompress the key. If this fails,
|
||||||
|
// we have database corruption!
|
||||||
|
data = decompressKey2(data);
|
||||||
|
script.fromPubkey(data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
br.offset -= 1;
|
||||||
|
size = br.readVarint() - 6;
|
||||||
|
if (size > 10000) {
|
||||||
|
// This violates consensus rules.
|
||||||
|
// We don't need to read it.
|
||||||
|
script.fromUnspendable();
|
||||||
|
p.seek(size);
|
||||||
|
} else {
|
||||||
|
data = br.readBytes(size);
|
||||||
|
script.fromRaw(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress an output.
|
||||||
|
* @param {Output|Coin} output
|
||||||
|
* @param {BufferWriter} bw
|
||||||
|
*/
|
||||||
|
|
||||||
|
function compressOutput(output, bw) {
|
||||||
|
compressScript(output.script, bw);
|
||||||
|
bw.writeVarint(output.value);
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a script from buffer reader.
|
||||||
|
* @param {Output|Coin} output
|
||||||
|
* @param {BufferReader} br
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decompressOutput(output, br) {
|
||||||
|
decompressScript(output.script, br);
|
||||||
|
output.value = br.readVarint();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip past a compressed output.
|
||||||
|
* @param {BufferWriter} bw
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function skipOutput(br) {
|
||||||
|
var start = br.offset;
|
||||||
|
|
||||||
|
// Skip past the compressed scripts.
|
||||||
|
switch (br.readU8()) {
|
||||||
|
case 0:
|
||||||
|
br.seek(br.readVarint());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
br.seek(20);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
br.seek(33);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Bad prefix.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip past the value.
|
||||||
|
br.skipVarint();
|
||||||
|
|
||||||
|
return br.offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress an output.
|
||||||
|
* @param {Output|Coin} output
|
||||||
|
* @param {BufferWriter} bw
|
||||||
|
*/
|
||||||
|
|
||||||
|
function compressOutput2(output, bw) {
|
||||||
|
compressScript2(output.script, bw);
|
||||||
|
bw.writeVarint(output.value);
|
||||||
|
return bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a script from buffer reader.
|
||||||
|
* @param {Output|Coin} output
|
||||||
|
* @param {BufferReader} br
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decompressOutput2(output, br) {
|
||||||
|
decompressScript2(output.script, br);
|
||||||
|
output.value = br.readVarint();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip past a compressed output.
|
||||||
|
* @param {BufferWriter} bw
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function skipOutput2(br) {
|
||||||
|
var start = br.offset;
|
||||||
|
|
||||||
|
// Skip past the compressed scripts.
|
||||||
|
switch (br.readU8()) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
br.seek(20);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
br.seek(32);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
br.offset -= 1;
|
||||||
|
br.seek(br.readVarint() - 6);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip past the value.
|
||||||
|
br.skipVarint();
|
||||||
|
|
||||||
|
return br.offset - start;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compress value using an exponent. Takes advantage of
|
* Compress value using an exponent. Takes advantage of
|
||||||
* the fact that many bitcoin values are divisible by 10.
|
* the fact that many bitcoin values are divisible by 10.
|
||||||
@ -236,18 +448,120 @@ function decompressKey(key) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a public key (no hybrid keys allowed).
|
||||||
|
* @param {Buffer} key
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function publicKeyVerify(key) {
|
||||||
|
if (key.length === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (key[0]) {
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
return key.length === 33;
|
||||||
|
case 0x04:
|
||||||
|
if (key.length !== 65)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ec.publicKeyVerify(key);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress a public key to coins compression format.
|
||||||
|
* @param {Buffer} key
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function compressKey2(key) {
|
||||||
|
var out;
|
||||||
|
|
||||||
|
switch (key[0]) {
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
// Key is already compressed.
|
||||||
|
out = key;
|
||||||
|
break;
|
||||||
|
case 0x04:
|
||||||
|
// Compress the key normally.
|
||||||
|
out = ec.publicKeyConvert(key, true);
|
||||||
|
// Store the oddness.
|
||||||
|
out[0] = 0x04 | (key[64] & 0x01);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Bad point format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(out.length === 33);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress a public key from the coins compression format.
|
||||||
|
* @param {Buffer} key
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function decompressKey2(key) {
|
||||||
|
var format = key[0];
|
||||||
|
var out;
|
||||||
|
|
||||||
|
assert(key.length === 33);
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case 0x02:
|
||||||
|
case 0x03:
|
||||||
|
return key;
|
||||||
|
case 0x04:
|
||||||
|
key[0] = 0x02;
|
||||||
|
break;
|
||||||
|
case 0x05:
|
||||||
|
key[0] = 0x03;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Bad point format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress the key, and off the
|
||||||
|
// low bits so publicKeyConvert
|
||||||
|
// actually understands it.
|
||||||
|
out = ec.publicKeyConvert(key, false);
|
||||||
|
|
||||||
|
// Reset the first byte so as not to
|
||||||
|
// mutate the original buffer.
|
||||||
|
key[0] = format;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.compress = {
|
exports.compress = {
|
||||||
|
output: compressOutput,
|
||||||
|
output2: compressOutput2,
|
||||||
script: compressScript,
|
script: compressScript,
|
||||||
|
script2: compressScript2,
|
||||||
value: compressValue,
|
value: compressValue,
|
||||||
key: compressKey
|
key: compressKey,
|
||||||
|
key2: compressKey2
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.decompress = {
|
exports.decompress = {
|
||||||
|
output: decompressOutput,
|
||||||
|
output2: decompressOutput2,
|
||||||
|
skip: skipOutput,
|
||||||
|
skip2: skipOutput2,
|
||||||
script: decompressScript,
|
script: decompressScript,
|
||||||
|
script2: decompressScript2,
|
||||||
value: decompressValue,
|
value: decompressValue,
|
||||||
key: decompressKey
|
key: decompressKey,
|
||||||
|
key2: decompressKey
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1500,6 +1500,28 @@ Script.isCode = function isCode(raw) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject properties from a unspendable script.
|
||||||
|
* @private
|
||||||
|
* @param {Buffer} key
|
||||||
|
*/
|
||||||
|
|
||||||
|
Script.prototype.fromUnspendable = function fromUnspendable() {
|
||||||
|
this.raw = new Buffer(1);
|
||||||
|
this.raw[0] = opcodes.OP_RETURN;
|
||||||
|
this.code.push(new Opcode(opcodes.OP_RETURN));
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an unspendable script.
|
||||||
|
* @returns {Script}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Script.fromUnspendable = function fromUnspendable() {
|
||||||
|
return new Script().fromUnspendable();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject properties from a pay-to-pubkey script.
|
* Inject properties from a pay-to-pubkey script.
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
@ -142,7 +142,7 @@ BufferReader.prototype.destroy = function destroy() {
|
|||||||
BufferReader.prototype.readU8 = function readU8() {
|
BufferReader.prototype.readU8 = function readU8() {
|
||||||
var ret;
|
var ret;
|
||||||
assert(this.offset + 1 <= this.data.length);
|
assert(this.offset + 1 <= this.data.length);
|
||||||
ret = this.data.readUInt8(this.offset, true);
|
ret = this.data[this.offset];
|
||||||
this.offset += 1;
|
this.offset += 1;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user