parsec-lib
Version:
transaction and block implementation
221 lines (183 loc) • 13 kB
JavaScript
'use strict';Object.defineProperty(exports, "__esModule", { value: true });var _createClass = function () {function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);}}return function (Constructor, protoProps, staticProps) {if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;};}();
/**
* Copyright (c) 2018-present, Parsec Labs (parseclabs.org)
*
* This source code is licensed under the GNU Affero General Public License,
* version 3, found in the LICENSE file in the root directory of this source
* tree.
*/
var _ethereumjsUtil = require('ethereumjs-util');var _ethereumjsUtil2 = _interopRequireDefault(_ethereumjsUtil);
var _assert = require('assert');var _assert2 = _interopRequireDefault(_assert);
var _fastEquals = require('fast-equals');
var _merkleTree = require('./merkleTree');var _merkleTree2 = _interopRequireDefault(_merkleTree);
var _util = require('./util');
var _transaction = require('./transaction');var _transaction2 = _interopRequireDefault(_transaction);function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _classCallCheck(instance, Constructor) {if (!(instance instanceof Constructor)) {throw new TypeError("Cannot call a class as a function");}}var
Block = function () {
function Block(height, options) {_classCallCheck(this, Block);var _ref =
options || {},timestamp = _ref.timestamp,txs = _ref.txs;
this.height = height;
this.timestamp = timestamp;
this.txList = txs || [];
this.txHashList = this.txList.map(function (tx) {return tx.hash();});
this.merkleTree = null;
}_createClass(Block, [{ key: 'addTx', value: function addTx(
tx) {
if (this.txHashList.indexOf(tx.hash()) > -1) {
throw Error('tx already contained');
}
this.txList.push(tx);
this.txHashList.push(tx.hash());
// invalidate existing tree
// todo: optimize not to recalculate the whole tree on every tx added
this.merkleTree = null;
return this;
} }, { key: 'getMerkleTree', value: function getMerkleTree()
{
if (!this.merkleTree) {
var hashBufs = this.txList.map(function (tx) {return tx.hashBuf();});
if (hashBufs.length % 2) {
hashBufs.push(Buffer.alloc(32, 0));
}
this.merkleTree = new _merkleTree2.default(hashBufs);
}
return this.merkleTree;
} }, { key: 'merkleRoot', value: function merkleRoot()
{
return this.getMerkleTree().getHexRoot().toString();
} }, { key: 'header', value: function header(
payload) {
(0, _util.writeUint64)(payload, this.height, 0);
payload.write(
this.merkleRoot().
replace('0x', '').
padStart(2, '0'), // fix for Invalid hex string error in browsers if merkleRoot is "0"
8,
'hex');
return payload;
} }, { key: 'hash', value: function hash()
{
var payload = this.header(Buffer.alloc(40, 0));
return _ethereumjsUtil2.default.bufferToHex(_ethereumjsUtil2.default.keccak256(payload));
}
// Returns serialized tx bytes as hex string
}, { key: 'hex', value: function hex() {
return (0, _util.toHexString)(this.toRaw());
} }, { key: 'equals', value: function equals(
another) {
return (0, _fastEquals.deepEqual)(this, another);
}
/*
* Returns raw block data size
*/ }, { key: 'getSize', value: function getSize()
{
// 8 bytes height + 4 bytes timestamp
// + for each tx( X bytes raw tx size + 4 bytes raw tx length )
return 12 + this.txList.reduce(function (s, tx) {return s + tx.getSize() + 4;}, 0);
} }, { key: 'toJSON', value: function toJSON()
{
return {
height: this.height,
timestamp: this.timestamp,
txs: this.txList.map(function (tx) {return tx.toJSON();}) };
} }, { key: 'toRaw',
/*
* Returns {Buffer} with raw serialized block bytes
*
* 8 bytes - height
* 4 bytes - timestamp
* [
* 4 bytes - tx 1 length,
* X bytes - tx 1 data,
* 4 bytes - tx 2 length,
* X bytes - tx 2 data,
* ...
* ]
*
* Also: `fromRaw`
*/value: function toRaw()
{
var payload = Buffer.alloc(this.getSize(), 0);
(0, _util.writeUint64)(payload, this.height, 0);
payload.writeUInt32BE(this.timestamp, 8);
var offset = 12;
var rawTx = void 0;
this.txList.forEach(function (tx) {
rawTx = tx.toRaw();
(0, _assert2.default)(rawTx.length <= Math.pow(2, 32));
payload.writeUInt32BE(rawTx.length, offset);
rawTx.copy(payload, offset + 4);
offset += rawTx.length + 4;
});
return payload;
}
/*
* Creates block from raw serialized bytes
* Also: `toRaw`
*/ }, { key: 'proof',
// Returns proof of block inclusion for given tx
// [
// 32b - blockHash
// 32b - (
// 1b tx data byte offset,
// 1b merkle proof 32b-slice offset,
// 2b tx length,
// 8b tx position index in the block,
// ..00..,
// Xb txData - up to 20 bytes of the first X bytes of tx data where X = txLength % 32
// ),
// 32b - txData,
// ... <more 32b tx data if needed>
// 32b - proof,
// ... <more 32b proofs if needed>
// ]
value: function proof(tx) {var proofOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var txData = tx.toRaw();
var pos = this.txHashList.indexOf(tx.hash());
if (pos < 0) {
throw Error('tx not in the block');
}
if (proofOffset > 0) {
// 1. find size of block (rounded pow 2)
var nextPow = this.txList.length % 2 ? this.txList.length : this.txList.length - 1;
nextPow |= nextPow >> 1; // eslint-disable-line no-bitwise
nextPow |= nextPow >> 2; // eslint-disable-line no-bitwise
nextPow |= nextPow >> 4; // eslint-disable-line no-bitwise
nextPow |= nextPow >> 8; // eslint-disable-line no-bitwise
nextPow |= nextPow >> 16; // eslint-disable-line no-bitwise
nextPow++;
// 2. shift proofOffset
// 3. add numbers
pos += proofOffset << Math.log2(nextPow); // eslint-disable-line no-bitwise
}
var slices = [];
slices.push(this.merkleRoot());
// Slices with tx metadata, v-sig and tx data
// metadata size is 12 bytes (see proof structure above)
var metadataAndTxLength = 12 + txData.length;
// align to 32 bytes, so that e.g. 38 bytes will be stored in 64 bytes buffer
var doubleWordAlignedLength = metadataAndTxLength + (32 - metadataAndTxLength % 32);
var txPayload = Buffer.alloc(doubleWordAlignedLength);
// tx length
txPayload.writeUInt32BE(txData.length, 0);
// byte offset of the tx data in proof bytes
var offset = 32 + (txPayload.length - txData.length);
txPayload.writeUInt8(offset, 0);
// offset of the merkle proof in array of 32byte slices
var sliceCount = Math.floor(txData.length / 32) + 1;
txPayload.writeUInt8((txData.length % 32 > 20 ? sliceCount + 1 : sliceCount) + 1, 1);
// tx index in the block
(0, _util.writeUint64)(txPayload, pos, 4);
// copy tx data tighly aligned to the right boundary of the buffer
txData.copy(txPayload, txPayload.length - txData.length, 0);
// split txdata buffer in 32 byte words as hex strings
for (var i = 0; i < doubleWordAlignedLength / 32; i++) {
slices.push(
(0, _ethereumjsUtil.bufferToHex)(txPayload.slice(i * 32, i * 32 + 32)));
}
// get merkle proofs
var proofs = this.getMerkleTree().
getProof(tx.hashBuf()).
map(function (buff) {return (0, _ethereumjsUtil.bufferToHex)(buff);});
// Add slices with proofs and return
return slices.concat(proofs);
} }], [{ key: 'fromJSON', value: function fromJSON(_ref2) {var height = _ref2.height,timestamp = _ref2.timestamp,txs = _ref2.txs;return new Block(height, { timestamp: timestamp, txs: txs.map(_transaction2.default.fromJSON) });} }, { key: 'fromRaw', value: function fromRaw(buf) {var height = (0, _util.readUint64)(buf);var timestamp = buf.readUInt32BE(8);var block = new Block(height, { timestamp: timestamp });var offset = 12;var txSize = void 0;while (offset < buf.length) {txSize = buf.readUInt32BE(offset);block.addTx(_transaction2.default.fromRaw(buf.slice(offset + 4, offset + 4 + txSize)));offset += txSize + 4;}return block;} }]);return Block;}();exports.default = Block;module.exports = exports['default'];