tx/coins: start using static writers more.

This commit is contained in:
Christopher Jeffrey 2016-12-11 13:03:21 -08:00
parent 1296bb2302
commit f7c9a24802
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
12 changed files with 287 additions and 97 deletions

View File

@ -7,7 +7,7 @@ var bench = require('./bench');
var raw = fs.readFileSync(__dirname + '/../test/data/wtx.hex', 'utf8');
var wtx = TX.fromRaw(raw.trim(), 'hex');
var coins = Coins.fromTX(wtx);
var coins = Coins.fromTX(wtx, 1);
var i, j, end, hash;
end = bench('serialize');

View File

@ -12,7 +12,8 @@ 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 StaticWriter = require('../utils/staticwriter');
var encoding = require('../utils/encoding');
var compressor = require('./compress');
var compress = compressor.compress;
var decompress = compressor.decompress;
@ -306,18 +307,17 @@ Coins.prototype.isEmpty = function isEmpty() {
*/
/**
* Write the coins object to writer.
* @param {BufferWriter} bw
* @returns {BufferWriter}
* Calculate header code.
* @param {Number} len
* @param {Number} size
* @returns {Number}
*/
Coins.prototype.toWriter = function toWriter(bw) {
var len = this.length();
var size = Math.floor((len + 5) / 8);
Coins.prototype.header = function header(len, size) {
var first = this.isUnspent(0);
var second = this.isUnspent(1);
var offset = 0;
var i, j, code, ch, output;
var code;
// Throw if we're fully spent.
assert(len !== 0, 'Cannot serialize fully-spent coins.');
@ -341,6 +341,22 @@ Coins.prototype.toWriter = function toWriter(bw) {
if (second)
code += 4;
return code;
};
/**
* Serialize the coins object.
* @returns {Buffer}
*/
Coins.prototype.toRaw = function toRaw() {
var len = this.length();
var size = Math.floor((len + 5) / 8);
var code = this.header(len, size);
var total = this.getSize(len, size, code);
var bw = new StaticWriter(total);
var i, j, ch, output;
// Write headers.
bw.writeVarint(this.version);
bw.writeU32(this.height);
@ -366,27 +382,49 @@ Coins.prototype.toWriter = function toWriter(bw) {
output.toWriter(bw);
}
return bw;
return bw.render();
};
/**
* Serialize the coins object.
* @returns {Buffer}
* Calculate coins size.
* @param {Number} code
* @param {Number} size
* @param {Number} len
* @returns {Number}
*/
Coins.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
Coins.prototype.getSize = function getSize(len, size, code) {
var total = 0;
var i, output;
total += encoding.sizeVarint(this.version);
total += 4;
total += encoding.sizeVarint(code);
total += size;
// Write the compressed outputs.
for (i = 0; i < len; i++) {
output = this.outputs[i];
if (!output || output.spent)
continue;
total += output.getSize();
}
return total;
};
/**
* Inject data from buffer reader.
* Inject data from serialized coins.
* @private
* @param {BufferReader} br
* @param {Buffer} data
* @param {Hash} hash
* @returns {Coins}
*/
Coins.prototype.fromReader = function fromReader(br, hash) {
Coins.prototype.fromRaw = function fromRaw(data, hash) {
var br = new BufferReader(data);
var first = null;
var second = null;
var i, j, code, size, offset, ch;
@ -437,18 +475,6 @@ Coins.prototype.fromReader = function fromReader(br, hash) {
return this;
};
/**
* Inject data from serialized coins.
* @private
* @param {Buffer} data
* @param {Hash} hash
* @returns {Coins}
*/
Coins.prototype.fromRaw = function fromRaw(data, hash) {
return this.fromReader(new BufferReader(data), hash);
};
/**
* Parse a single serialized coin.
* @param {Buffer} data
@ -519,17 +545,6 @@ Coins.parseCoin = function parseCoin(data, hash, index) {
}
};
/**
* Instantiate coins from a buffer reader.
* @param {Buffer} data
* @param {Hash} hash - Transaction hash.
* @returns {Coins}
*/
Coins.fromReader = function fromReader(br, hash) {
return new Coins().fromReader(br, hash);
};
/**
* Instantiate coins from a buffer.
* @param {Buffer} data
@ -668,6 +683,18 @@ CoinEntry.prototype.toOutput = function toOutput() {
return this.output;
};
/**
* Calculate coin entry size.
* @returns {Number}
*/
CoinEntry.prototype.getSize = function getSize() {
if (!this.raw)
return compress.size(this.output);
return this.size;
};
/**
* Slice off the part of the buffer
* relevant to this particular coin.

View File

@ -8,6 +8,7 @@
var assert = require('assert');
var ec = require('../crypto/ec');
var encoding = require('../utils/encoding');
/*
* Constants
@ -115,6 +116,33 @@ function decompressScript(script, br) {
return script;
}
/**
* Calculate script size.
* @returns {Number}
*/
function sizeScript(script) {
var size, data;
if (script.isPubkeyhash(true))
return 21;
if (script.isScripthash())
return 21;
if (script.isPubkey(true)) {
data = script.code[0].data;
if (publicKeyVerify(data))
return 33;
}
size = 0;
size += encoding.sizeVarint(script.raw.length + COMPRESS_TYPES);
size += script.raw.length;
return size;
}
/**
* Compress an output.
* @param {Output} output
@ -139,6 +167,18 @@ function decompressOutput(output, br) {
return output;
}
/**
* Calculate output size.
* @returns {Number}
*/
function sizeOutput(output) {
var size = 0;
size += encoding.sizeVarint(output.value);
size += sizeScript(output.script);
return size;
}
/**
* Compress an output.
* @param {Coin} coin
@ -356,6 +396,7 @@ function decompressKey(key) {
exports.compress = {
output: compressOutput,
coin: compressCoin,
size: sizeOutput,
script: compressScript,
value: compressValue,
key: compressKey

View File

@ -8,7 +8,8 @@
var assert = require('assert');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var encoding = require('../utils/encoding');
var Output = require('../primitives/output');
var Coins = require('./coins');
var compressor = require('./compress');
@ -43,13 +44,33 @@ UndoCoins.prototype.push = function push(entry) {
this.items.push(undo);
};
/**
* Calculate undo coins size.
* @returns {Number}
*/
UndoCoins.prototype.getSize = function getSize() {
var size = 0;
var i, coin;
size += 4;
for (i = 0; i < this.items.length; i++) {
coin = this.items[i];
size += coin.getSize();
}
return size;
};
/**
* Serialize all undo coins.
* @returns {Buffer}
*/
UndoCoins.prototype.toRaw = function toRaw() {
var bw = new BufferWriter();
var size = this.getSize();
var bw = new StaticWriter(size);
var i, coin;
bw.writeU32(this.items.length);
@ -184,6 +205,33 @@ UndoCoin.prototype.toOutput = function toOutput() {
return this.output;
};
/**
* Calculate undo coin size.
* @returns {Number}
*/
UndoCoin.prototype.getSize = function getSize() {
var height = this.height;
var size = 0;
if (height === -1)
height = 0;
size += encoding.sizeVarint(height * 2 + (this.coinbase ? 1 : 0));
if (this.height !== -1)
size += encoding.sizeVarint(this.version);
if (this.entry) {
// Cached from spend.
size += this.entry.getSize();
} else {
size += compress.size(this.output);
}
return size;
};
/**
* Write the undo coin to a buffer writer.
* @param {BufferWriter} bw
@ -220,7 +268,8 @@ UndoCoin.prototype.toWriter = function toWriter(bw) {
*/
UndoCoin.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
/**

View File

@ -13,7 +13,7 @@ var util = require('../utils/util');
var crypto = require('../crypto/crypto');
var btcutils = require('../btc/utils');
var VerifyResult = require('../btc/errors').VerifyResult;
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var InvItem = require('./invitem');
/**
@ -160,7 +160,7 @@ AbstractBlock.prototype.hash = function hash(enc) {
*/
AbstractBlock.prototype.abbr = function abbr() {
var bw = new BufferWriter();
var bw = new StaticWriter(80);
bw.writeU32(this.version);
bw.writeHash(this.prevBlock);
bw.writeHash(this.merkleRoot);

View File

@ -14,7 +14,7 @@ var Network = require('../protocol/network');
var Script = require('../script/script');
var Witness = require('../script/witness');
var Outpoint = require('./outpoint');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var BufferReader = require('../utils/reader');
/**
@ -335,7 +335,8 @@ Input.prototype.getSize = function getSize() {
*/
Input.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
/**

View File

@ -9,7 +9,7 @@
var util = require('../utils/util');
var assert = require('assert');
var constants = require('../protocol/constants');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
/**
@ -124,7 +124,7 @@ Outpoint.prototype.getSize = function getSize() {
*/
Outpoint.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
return this.toWriter(new StaticWriter(36)).render();
};
/**

View File

@ -13,7 +13,7 @@ var btcutils = require('../btc/utils');
var Amount = require('../btc/amount');
var Network = require('../protocol/network');
var Script = require('../script/script');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var BufferReader = require('../utils/reader');
var assert = require('assert');
@ -246,7 +246,8 @@ Output.prototype.toWriter = function toWriter(bw) {
*/
Output.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
/**

View File

@ -9,6 +9,7 @@
var assert = require('assert');
var util = require('../utils/util');
var encoding = require('../utils/encoding');
var co = require('../utils/co');
var crypto = require('../crypto/crypto');
var btcutils = require('../btc/utils');
@ -17,6 +18,7 @@ var constants = require('../protocol/constants');
var Network = require('../protocol/network');
var Script = require('../script/script');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var VerifyResult = require('../btc/errors').VerifyResult;
var Input = require('./input');
var Output = require('./output');
@ -432,8 +434,7 @@ TX.prototype.signatureHash = function signatureHash(index, prev, value, type, ve
*/
TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
var i, input, output;
var bw = new BufferWriter();
var i, size, bw, input, output;
var hashType = type & 0x1f;
if (hashType === constants.hashType.SINGLE) {
@ -446,8 +447,14 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
// Remove all code separators.
prev = prev.removeSeparators();
// Calculate buffer size.
size = this.hashSize(index, prev, type);
bw = new StaticWriter(size);
bw.writeU32(this.version);
// Serialize inputs.
if (type & constants.hashType.ANYONECANPAY) {
bw.writeVarint(1);
@ -482,48 +489,56 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
bw.writeVarint(0);
// Sequences are 0 if NONE or SINGLE.
if (hashType === constants.hashType.NONE
|| hashType === constants.hashType.SINGLE) {
bw.writeU32(0);
} else {
bw.writeU32(input.sequence);
switch (hashType) {
case constants.hashType.NONE:
case constants.hashType.SINGLE:
bw.writeU32(0);
break;
default:
bw.writeU32(input.sequence);
break;
}
}
}
if (hashType === constants.hashType.NONE) {
// No outputs if NONE.
bw.writeVarint(0);
} else if (hashType === constants.hashType.SINGLE) {
// Drop all outputs after the
// current input index if SINGLE.
bw.writeVarint(index + 1);
// Serialize outputs.
switch (hashType) {
case constants.hashType.NONE:
// No outputs if NONE.
bw.writeVarint(0);
break;
case constants.hashType.SINGLE:
// Drop all outputs after the
// current input index if SINGLE.
bw.writeVarint(index + 1);
for (i = 0; i < index + 1; i++) {
output = this.outputs[i];
for (i = 0; i < index + 1; i++) {
output = this.outputs[i];
// Regular serialization if
// at current input index.
if (i === index) {
// Regular serialization if
// at current input index.
if (i === index) {
bw.write64(output.value);
bw.writeVarBytes(output.script.toRaw());
continue;
}
// Null all outputs not at
// current input index.
bw.write64(-1);
bw.writeVarint(0);
}
break;
default:
// Regular output serialization if ALL.
bw.writeVarint(this.outputs.length);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
bw.write64(output.value);
bw.writeVarBytes(output.script.toRaw());
continue;
}
// Null all outputs not at
// current input index.
bw.write64(-1);
bw.writeVarint(0);
}
} else {
// Regular output serialization if ALL.
bw.writeVarint(this.outputs.length);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
bw.write64(output.value);
bw.writeVarBytes(output.script.toRaw());
}
break;
}
bw.writeU32(this.locktime);
@ -534,6 +549,57 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
return crypto.hash256(bw.render());
};
/**
* Calculate sighash size.
* @private
* @param {Number} index
* @param {Script} prev
* @param {Number} type
* @returns {Number}
*/
TX.prototype.hashSize = function hashSize(index, prev, type) {
var size = 0;
var i, output;
size += 4;
if (type & constants.hashType.ANYONECANPAY) {
size += 1;
size += 36;
size += prev.getVarSize();
size += 4;
} else {
size += encoding.sizeVarint(this.inputs.length);
size += 41 * (this.inputs.length - 1);
size += 36;
size += prev.getVarSize();
size += 4;
}
switch (type & 0x1f) {
case constants.hashType.NONE:
size += 1;
break;
case constants.hashType.SINGLE:
size += encoding.sizeVarint(index + 1);
size += 9 * index;
size += this.outputs[index].getSize();
break;
default:
size += encoding.sizeVarint(this.outputs.length);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
size += output.getSize();
}
break;
}
size += 8;
return size;
};
/**
* Witness sighashing -- O(n).
* @private
@ -544,13 +610,13 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) {
*/
TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type) {
var i, bw, input, output, prevouts, sequences, outputs;
var i, bw, size, input, output, prevouts, sequences, outputs;
if (!(type & constants.hashType.ANYONECANPAY)) {
if (this._hashPrevouts) {
prevouts = this._hashPrevouts;
} else {
bw = new BufferWriter();
bw = new StaticWriter(this.inputs.length * 36);
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
@ -572,7 +638,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type
if (this._hashSequence) {
sequences = this._hashSequence;
} else {
bw = new BufferWriter();
bw = new StaticWriter(this.inputs.length * 4);
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
@ -593,7 +659,14 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type
if (this._hashOutputs) {
outputs = this._hashOutputs;
} else {
bw = new BufferWriter();
size = 0;
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
size += output.getSize();
}
bw = new StaticWriter(size);
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
@ -614,7 +687,8 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type
input = this.inputs[index];
bw = new BufferWriter();
size = 156 + prev.getVarSize();
bw = new StaticWriter(size);
bw.writeU32(this.version);
bw.writeBytes(prevouts);

View File

@ -13,7 +13,6 @@ var constants = require('../protocol/constants');
var util = require('../utils/util');
var encoding = require('./encoding');
var BufferReader = require('../utils/reader');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var opcodes = constants.opcodes;

View File

@ -19,9 +19,8 @@ var Script = require('./script');
var encoding = require('./encoding');
var enc = require('../utils/encoding');
var Opcode = require('./opcode');
var BufferWriter = require('../utils/writer');
var StaticWriter = require('../utils/staticwriter');
var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter');
var Address = require('../primitives/address');
var Stack = require('./stack');

View File

@ -1,6 +1,5 @@
/*!
* writer.js - buffer writer for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* staticwriter.js - buffer writer for bcoin
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
@ -43,7 +42,7 @@ function StaticWriter(size) {
StaticWriter.prototype.render = function render(keep) {
var data = this.data;
assert.equal(this.written, data.length);
assert(this.written === data.length);
if (!keep)
this.destroy();