serialization: add size calculation and static writer.

This commit is contained in:
Christopher Jeffrey 2016-12-11 12:19:18 -08:00
parent 808d8678a6
commit 1296bb2302
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 541 additions and 5 deletions

View File

@ -319,6 +319,15 @@ Input.fromJSON = function fromJSON(json) {
return new Input().fromJSON(json);
};
/**
* Calculate size of serialized input.
* @returns {Number}
*/
Input.prototype.getSize = function getSize() {
return 40 + this.script.getVarSize();
};
/**
* Serialize the input.
* @param {String?} enc - Encoding, can be `'hex'` or null.

View File

@ -109,6 +109,15 @@ Outpoint.prototype.toWriter = function toWriter(bw) {
return bw;
};
/**
* Calculate size of outpoint.
* @returns {Number}
*/
Outpoint.prototype.getSize = function getSize() {
return 36;
};
/**
* Serialize outpoint.
* @returns {Buffer}

View File

@ -192,7 +192,7 @@ Output.prototype.getDustThreshold = function getDustThreshold(rate) {
*/
Output.prototype.getSize = function getSize() {
return this.toWriter(new BufferWriter()).written;
return 8 + this.script.getVarSize();
};
/**

View File

@ -14,6 +14,7 @@ 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;
/**
@ -85,7 +86,32 @@ Opcode.prototype.toWriter = function toWriter(bw) {
*/
Opcode.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
/**
* Calculate opcode size.
* @returns {Number}
*/
Opcode.prototype.getSize = function getSize() {
if (!this.data)
return 1;
if (this.value <= 0x4b)
return 1 + this.data.length;
switch (this.value) {
case opcodes.OP_PUSHDATA1:
return 2 + this.data.length;
case opcodes.OP_PUSHDATA2:
return 3 + this.data.length;
case opcodes.OP_PUSHDATA4:
return 5 + this.data.length;
default:
throw new Error('Unknown pushdata opcode.');
}
};
/**

View File

@ -14,6 +14,7 @@ var crypto = require('../crypto/crypto');
var assert = require('assert');
var BufferWriter = require('../utils/writer');
var BufferReader = require('../utils/reader');
var StaticWriter = require('../utils/staticwriter');
var opcodes = constants.opcodes;
var STACK_TRUE = new Buffer([1]);
var STACK_FALSE = new Buffer(0);
@ -25,6 +26,7 @@ var Opcode = require('./opcode');
var Stack = require('./stack');
var sigcache = require('./sigcache');
var encoding = require('./encoding');
var enc = require('../utils/encoding');
var ec = require('../crypto/ec');
var Address = require('../primitives/address');
@ -251,7 +253,8 @@ Script.prototype.toASM = function toASM(decode) {
*/
Script.prototype.compile = function compile() {
var bw = new BufferWriter();
var size = this.getCodeSize();
var bw = new StaticWriter(size);
var i, op;
for (i = 0; i < this.code.length; i++) {
@ -1942,7 +1945,8 @@ Script.prototype.isStandard = function isStandard() {
};
/**
* Calculate size of script excluding the varint size bytes.
* Calculate the size of the script
* excluding the varint size bytes.
* @returns {Number}
*/
@ -1950,6 +1954,33 @@ Script.prototype.getSize = function getSize() {
return this.raw.length;
};
/**
* Calculate the size of the script
* including the varint size bytes.
* @returns {Number}
*/
Script.prototype.getVarSize = function getVarSize() {
return enc.sizeVarint(this.raw.length) + this.raw.length;
};
/**
* Calculate size of code to be compiled.
* @returns {Number}
*/
Script.prototype.getCodeSize = function getCodeSize() {
var size = 0;
var i, op;
for (i = 0; i < this.code.length; i++) {
op = this.code[i];
size += op.getSize();
}
return size;
};
/**
* "Guess" the address of the input script.
* This method is not 100% reliable.

View File

@ -17,8 +17,10 @@ var STACK_NEGATE = new Buffer([0x81]);
var scriptTypes = constants.scriptTypes;
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 Address = require('../primitives/address');
var Stack = require('./stack');
@ -303,6 +305,35 @@ Witness.prototype.indexOf = function indexOf(data) {
return util.indexOf(this.items, data);
};
/**
* Calculate size of the witness
* excluding the varint size bytes.
* @returns {Number}
*/
Witness.prototype.getSize = function getSize() {
var size = 0;
var i, item;
for (i = 0; i < this.items.length; i++) {
item = this.items[i];
size += enc.sizeVarint(item.length);
size += item.length;
}
return size;
};
/**
* Calculate size of the witness
* including the varint size bytes.
* @returns {Number}
*/
Witness.prototype.getVarSize = function getVarSize() {
return enc.sizeVarint(this.items.length) + this.getSize();
};
/**
* Write witness to a buffer writer.
* @param {BufferWriter} bw
@ -326,7 +357,8 @@ Witness.prototype.toWriter = function toWriter(bw) {
*/
Witness.prototype.toRaw = function toRaw() {
return this.toWriter(new BufferWriter()).render();
var size = this.getSize();
return this.toWriter(new StaticWriter(size)).render();
};
/**

429
lib/utils/staticwriter.js Normal file
View File

@ -0,0 +1,429 @@
/*!
* writer.js - buffer writer for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var encoding = require('./encoding');
var crypto = require('../crypto/crypto');
/**
* An object that allows writing of buffers in a
* sane manner. This buffer writer is extremely
* optimized since it does not actually write
* anything until `render` is called. It makes
* one allocation: at the end, once it knows the
* size of the buffer to be allocated. Because
* of this, it can also act as a size calculator
* which is useful for guaging block size
* without actually serializing any data.
* @exports StaticWriter
* @constructor
* @param {(StaticWriter|Object)?} options
*/
function StaticWriter(size) {
if (!(this instanceof StaticWriter))
return new StaticWriter(size);
this.data = new Buffer(size);
this.written = 0;
}
/**
* Allocate and render the final buffer.
* @param {Boolean?} keep - Do not destroy the writer.
* @returns {Buffer} Rendered buffer.
*/
StaticWriter.prototype.render = function render(keep) {
var data = this.data;
assert.equal(this.written, data.length);
if (!keep)
this.destroy();
return data;
};
/**
* Get size of data written so far.
* @returns {Number}
*/
StaticWriter.prototype.getSize = function getSize() {
return this.written;
};
/**
* Seek to relative offset.
* @param {Number} offset
*/
StaticWriter.prototype.seek = function seek(offset) {
this.written += offset;
};
/**
* Destroy the buffer writer.
*/
StaticWriter.prototype.destroy = function destroy() {
this.data = null;
this.written = null;
};
/**
* Write uint8.
* @param {Number} value
*/
StaticWriter.prototype.writeU8 = function writeU8(value) {
this.written = this.data.writeUInt8(value, this.written, true);
};
/**
* Write uint16le.
* @param {Number} value
*/
StaticWriter.prototype.writeU16 = function writeU16(value) {
this.written = this.data.writeUInt16LE(value, this.written, true);
};
/**
* Write uint16be.
* @param {Number} value
*/
StaticWriter.prototype.writeU16BE = function writeU16BE(value) {
this.written = this.data.writeUInt16BE(value, this.written, true);
};
/**
* Write uint32le.
* @param {Number} value
*/
StaticWriter.prototype.writeU32 = function writeU32(value) {
this.written = this.data.writeUInt32LE(value, this.written, true);
};
/**
* Write uint32be.
* @param {Number} value
*/
StaticWriter.prototype.writeU32BE = function writeU32BE(value) {
this.written = this.data.writeUInt32BE(value, this.written, true);
};
/**
* Write uint64le.
* @param {Number} value
*/
StaticWriter.prototype.writeU64 = function writeU64(value) {
this.written = encoding.writeU64(this.data, value, this.written);
};
/**
* Write uint64be.
* @param {Number} value
*/
StaticWriter.prototype.writeU64BE = function writeU64BE(value) {
this.written = encoding.writeU64BE(this.data, value, this.written);
};
/**
* Write uint64le.
* @param {BN} value
*/
StaticWriter.prototype.writeU64BN = function writeU64BN(value) {
assert(false, 'Not implemented.');
};
/**
* Write uint64be.
* @param {BN} value
*/
StaticWriter.prototype.writeU64BEBN = function writeU64BEBN(value) {
assert(false, 'Not implemented.');
};
/**
* Write int8.
* @param {Number} value
*/
StaticWriter.prototype.write8 = function write8(value) {
this.written = this.data.writeInt8(value, this.written, true);
};
/**
* Write int16le.
* @param {Number} value
*/
StaticWriter.prototype.write16 = function write16(value) {
this.written = this.data.writeInt16LE(value, this.written, true);
};
/**
* Write int16be.
* @param {Number} value
*/
StaticWriter.prototype.write16BE = function write16BE(value) {
this.written = this.data.writeInt16BE(value, this.written, true);
};
/**
* Write int32le.
* @param {Number} value
*/
StaticWriter.prototype.write32 = function write32(value) {
this.written = this.data.writeInt32LE(value, this.written, true);
};
/**
* Write int32be.
* @param {Number} value
*/
StaticWriter.prototype.write32BE = function write32BE(value) {
this.written = this.data.writeInt32BE(value, this.written, true);
};
/**
* Write int64le.
* @param {Number} value
*/
StaticWriter.prototype.write64 = function write64(value) {
this.written = encoding.write64(this.data, value, this.written);
};
/**
* Write int64be.
* @param {Number} value
*/
StaticWriter.prototype.write64BE = function write64BE(value) {
this.written = encoding.write64BE(this.data, value, this.written);
};
/**
* Write int64le.
* @param {BN} value
*/
StaticWriter.prototype.write64BN = function write64BN(value) {
assert(false, 'Not implemented.');
};
/**
* Write int64be.
* @param {BN} value
*/
StaticWriter.prototype.write64BEBN = function write64BEBN(value) {
assert(false, 'Not implemented.');
};
/**
* Write float le.
* @param {Number} value
*/
StaticWriter.prototype.writeFloat = function writeFloat(value) {
this.written = this.data.writeFloatLE(value, this.written, true);
};
/**
* Write float be.
* @param {Number} value
*/
StaticWriter.prototype.writeFloatBE = function writeFloatBE(value) {
this.written = this.data.writeFloatBE(value, this.written, true);
};
/**
* Write double le.
* @param {Number} value
*/
StaticWriter.prototype.writeDouble = function writeDouble(value) {
this.written = this.data.writeDoubleLE(value, this.written, true);
};
/**
* Write double be.
* @param {Number} value
*/
StaticWriter.prototype.writeDoubleBE = function writeDoubleBE(value) {
this.written = this.data.writeDoubleBE(value, this.written, true);
};
/**
* Write a varint.
* @param {Number} value
*/
StaticWriter.prototype.writeVarint = function writeVarint(value) {
this.written = encoding.writeVarint(this.data, value, this.written);
};
/**
* Write a varint.
* @param {BN} value
*/
StaticWriter.prototype.writeVarintBN = function writeVarintBN(value) {
assert(false, 'Not implemented.');
};
/**
* Write a varint (type 2).
* @param {Number} value
*/
StaticWriter.prototype.writeVarint2 = function writeVarint2(value) {
this.written = encoding.writeVarint2(this.data, value, this.written);
};
/**
* Write a varint (type 2).
* @param {BN} value
*/
StaticWriter.prototype.writeVarint2BN = function writeVarint2BN(value) {
assert(false, 'Not implemented.');
};
/**
* Write bytes.
* @param {Buffer} value
*/
StaticWriter.prototype.writeBytes = function writeBytes(value) {
if (value.length === 0)
return;
this.written += value.copy(this.data, this.written);
};
/**
* Write bytes with a varint length before them.
* @param {Buffer} value
*/
StaticWriter.prototype.writeVarBytes = function writeVarBytes(value) {
this.writeVarint(value.length);
if (value.length === 0)
return;
this.writeBytes(value);
};
/**
* Write string to buffer.
* @param {String|Buffer} value
* @param {String?} enc - Any buffer-supported encoding.
*/
StaticWriter.prototype.writeString = function writeString(value, enc) {
if (typeof value !== 'string')
return this.writeBytes(value);
if (value.length === 0)
return;
this.written += this.data.write(value, this.written, enc);
};
/**
* Write a hash/hex-string.
* @param {Hash|Buffer}
*/
StaticWriter.prototype.writeHash = function writeHash(value) {
this.writeString(value, 'hex');
};
/**
* Write a string with a varint length before it.
* @param {String|Buffer}
* @param {String?} enc - Any buffer-supported encoding.
*/
StaticWriter.prototype.writeVarString = function writeVarString(value, enc) {
var size;
if (typeof value !== 'string')
return this.writeVarBytes(value);
size = Buffer.byteLength(value, enc);
this.writeVarint(size);
if (value.length === 0)
return;
this.writeString(value, enc);
};
/**
* Write a null-terminated string.
* @param {String|Buffer}
* @param {String?} enc - Any buffer-supported encoding.
*/
StaticWriter.prototype.writeNullString = function writeNullString(value, enc) {
this.writeString(value, enc);
this.writeU8(0);
};
/**
* Calculate and write a checksum for the data written so far.
*/
StaticWriter.prototype.writeChecksum = function writeChecksum() {
var data = this.data.slice(0, this.written);
var hash = crypto.hash256(data);
this.written += hash.copy(this.data, this.written, 0, 4);
};
/**
* Fill N bytes with value.
* @param {Number} value
* @param {Number} size
*/
StaticWriter.prototype.fill = function fill(value, size) {
assert(size >= 0);
if (size === 0)
return;
this.data.fill(value, this.written, this.written + size);
this.written += size;
};
/*
* Expose
*/
module.exports = StaticWriter;