UNPKG

neo-blockchain-offline

Version:

Utilities for loading offline blockchain data into a NEO Blockchain.

515 lines (435 loc) 14.6 kB
import { BinaryReader, Block } from 'neo-blockchain-core'; import { Transform } from 'stream'; import _ from 'lodash'; import fs from 'fs'; import zlib from 'zlib'; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _next(value) { step("next", value); } function _throw(err) { step("throw", err); } _next(); }); }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } var _fixBabelExtend = function (O) { var gOPD = O.getOwnPropertyDescriptor, gPO = O.getPrototypeOf || function (o) { return o.__proto__; }, sPO = O.setPrototypeOf || function (o, p) { o.__proto__ = p; return o; }, construct = _typeof(Reflect) === 'object' ? Reflect.construct : function (Parent, args, Class) { var Constructor, a = [null]; a.push.apply(a, args); Constructor = Parent.bind.apply(Parent, a); return sPO(new Constructor(), Class.prototype); }; return function fixBabelExtend(Class) { var Parent = gPO(Class); return sPO(Class, sPO(function Super() { return construct(Parent, arguments, gPO(this).constructor); }, Parent)); }; }(Object); var InvalidBlockTransformEncodingError = _fixBabelExtend( /*#__PURE__*/ function (_Error) { _inherits(InvalidBlockTransformEncodingError, _Error); function InvalidBlockTransformEncodingError() { _classCallCheck(this, InvalidBlockTransformEncodingError); return _possibleConstructorReturn(this, (InvalidBlockTransformEncodingError.__proto__ || Object.getPrototypeOf(InvalidBlockTransformEncodingError)).call(this, 'Invalid Block Transform Encoding.')); } return InvalidBlockTransformEncodingError; }(Error)); var SIZE_OF_INT32 = 4; var BlockTransform = /*#__PURE__*/ function (_Transform) { _inherits(BlockTransform, _Transform); function BlockTransform(context) { var _this; _classCallCheck(this, BlockTransform); _this = _possibleConstructorReturn(this, (BlockTransform.__proto__ || Object.getPrototypeOf(BlockTransform)).call(this, { readableObjectMode: true })); _this.context = context; _this.buffer = Buffer.from([]); return _this; } _createClass(BlockTransform, [{ key: "_transform", value: function _transform(chunk, encoding, callback) { var _this2 = this; if (typeof chunk === 'string' || encoding !== 'buffer') { throw new InvalidBlockTransformEncodingError(); } this.buffer = Buffer.concat([this.buffer, chunk]); try { var _processBuffer2 = this._processBuffer(new BinaryReader(this.buffer)), remainingBuffer = _processBuffer2.remainingBuffer, blocks = _processBuffer2.blocks; this.buffer = remainingBuffer; blocks.reverse().forEach(function (block) { return _this2.push(block); }); callback(null); } catch (error) { callback(error); } } }, { key: "_processBuffer", value: function _processBuffer(reader) { if (reader.remaining < SIZE_OF_INT32) { return { remainingBuffer: reader.remainingBuffer, blocks: [] }; } var length = reader.clone().readInt32LE(); // TODO: Not sure why this doesn't work properly with just length... if (reader.remaining + SIZE_OF_INT32 < length * 2) { return { remainingBuffer: reader.remainingBuffer, blocks: [] }; } reader.readInt32LE(); var block = Block.deserializeWireBase({ context: this.context, reader: reader }); var _processBuffer3 = this._processBuffer(reader), remainingBuffer = _processBuffer3.remainingBuffer, blocks = _processBuffer3.blocks; blocks.push(block); return { remainingBuffer: remainingBuffer, blocks: blocks }; } }]); return BlockTransform; }(Transform); var getStream = function getStream(chain) { var stream$$1 = fs.createReadStream(chain.path); if (chain.format === 'zip') { return stream$$1.pipe(zlib.createUnzip()); } return stream$$1; }; var getCount = function () { var _ref = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee(stream$$1) { var count; return regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: count = stream$$1.read(4); case 1: if (!(count == null)) { _context.next = 7; break; } _context.next = 4; return new Promise(function (resolve) { return setTimeout(function () { return resolve(); }, 250); }); case 4: count = stream$$1.read(4); _context.next = 1; break; case 7: return _context.abrupt("return", count.readUInt32LE(0)); case 8: case "end": return _context.stop(); } } }, _callee, this); })); return function getCount(_x) { return _ref.apply(this, arguments); }; }(); var loadChain = function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee2(_ref2) { var blockchain, chain; return regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: blockchain = _ref2.blockchain, chain = _ref2.chain; return _context2.abrupt("return", new Promise(function (resolve, reject) { var stream$$1 = getStream(chain); var transform = new BlockTransform(blockchain.deserializeWireContext); var cleanup = function cleanup() { stream$$1.unpipe(transform); }; var resolved = false; var rejected = false; var onError = function onError(error) { if (!resolved && !rejected) { rejected = true; cleanup(); reject(error); } }; var onEnd = function onEnd() { if (!resolved && !rejected) { resolved = true; cleanup(); resolve(); } }; stream$$1.once('error', onError); transform.once('finish', onEnd); transform.once('end', onEnd); transform.once('close', onEnd); transform.once('error', onError); // TODO: Not sure why I can't get this to work with a Writable stream so // just implement janky custom backpressure control var pending = 0; var paused = false; transform.on('data', function (block) { pending += 1; blockchain.persistBlock({ block: block, unsafe: true }).then(function () { pending -= 1; if (pending < 500 && paused) { paused = false; transform.resume(); } }).catch(onError); if (pending > 1000) { paused = true; transform.pause(); } }); getCount(stream$$1).then(function (count) { if (count > blockchain.currentBlockIndex) { stream$$1.pipe(transform); } else { resolved = true; // $FlowFixMe stream$$1.destroy(); resolve(); } }).catch(reject); })); case 2: case "end": return _context2.stop(); } } }, _callee2, this); })); return function loadChain(_x2) { return _ref3.apply(this, arguments); }; }(); var writeOut = function () { var _ref4 = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee3(blockchain, out, height) { var processed, start, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, _chunk, blocks; return regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: processed = 0; start = Date.now(); _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; _context3.prev = 5; _iterator = _.chunk(_.range(0, height), 10000)[Symbol.iterator](); case 7: if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { _context3.next = 16; break; } _chunk = _step.value; _context3.next = 11; return Promise.all(_chunk.map(function (index) { return blockchain.block.get({ hashOrIndex: index }); })); case 11: blocks = _context3.sent; // eslint-disable-next-line blocks.forEach(function (block) { var buffer = block.serializeWire(); var length = Buffer.alloc(4, 0); length.writeInt32LE(buffer.length, 0); out.write(length); out.write(buffer); processed += 1; if (processed >= 100000) { // eslint-disable-next-line console.log("Processed ".concat(processed, " blocks in ").concat(Date.now() - start, " ms")); processed = 0; start = Date.now(); } }); case 13: _iteratorNormalCompletion = true; _context3.next = 7; break; case 16: _context3.next = 22; break; case 18: _context3.prev = 18; _context3.t0 = _context3["catch"](5); _didIteratorError = true; _iteratorError = _context3.t0; case 22: _context3.prev = 22; _context3.prev = 23; if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } case 25: _context3.prev = 25; if (!_didIteratorError) { _context3.next = 28; break; } throw _iteratorError; case 28: return _context3.finish(25); case 29: return _context3.finish(22); case 30: out.end(); case 31: case "end": return _context3.stop(); } } }, _callee3, this, [[5, 18, 22, 30], [23,, 25, 29]]); })); return function writeOut(_x3, _x4, _x5) { return _ref4.apply(this, arguments); }; }(); var dumpChain = function () { var _ref6 = _asyncToGenerator( /*#__PURE__*/ regeneratorRuntime.mark(function _callee4(_ref5) { var blockchain, path, height; return regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: blockchain = _ref5.blockchain, path = _ref5.path; height = blockchain.currentBlockIndex; _context4.next = 4; return new Promise(function (resolve, reject) { var out = fs.createWriteStream(path); out.once('open', function () { var count = Buffer.alloc(4, 0); count.writeUInt32LE(height, 0); out.write(count); writeOut(blockchain, out, height).then(resolve, reject); }); }); case 4: case "end": return _context4.stop(); } } }, _callee4, this); })); return function dumpChain(_x6) { return _ref6.apply(this, arguments); }; }(); export { InvalidBlockTransformEncodingError, loadChain, dumpChain }; //# sourceMappingURL=es.js.map