UNPKG

madeline-ton

Version:

Pure JS client-side implementation of the Telegram TON blockchain protocol

643 lines (524 loc) 20.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objects = _interopRequireDefault(require("./TL/objects")); var _parser = _interopRequireDefault(require("./TL/parser")); var _stream = _interopRequireDefault(require("./TL/stream")); var _adnlConnection = _interopRequireDefault(require("./adnl-connection")); var _random = require("./crypto-sync/random"); var _deepEqual = _interopRequireDefault(require("deep-equal")); var _tools = require("./tools"); var _crypto = _interopRequireDefault(require("./crypto")); var _crypto2 = require("./crypto-sync/crypto"); var _ton_api = _interopRequireDefault(require("./config/ton_api.json")); var _lite_api = _interopRequireDefault(require("./config/lite_api.json")); var _tonLiteClientTest1Config = _interopRequireDefault(require("./config/ton-lite-client-test1.config.json")); var _bitstream = _interopRequireDefault(require("./boc/bitstream")); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var Lite = /*#__PURE__*/ function () { function Lite(settings) { var _this = this; (0, _classCallCheck2["default"])(this, Lite); (0, _defineProperty2["default"])(this, "min_ls_version", 0x101); (0, _defineProperty2["default"])(this, "min_ls_capabilities", 1); (0, _defineProperty2["default"])(this, "server", { ok: false }); (0, _defineProperty2["default"])(this, "knownBlockIds", []); (0, _defineProperty2["default"])(this, "printedBlockIds", 0); (0, _defineProperty2["default"])(this, "connections", []); settings = _objectSpread({}, settings, { schemes: { 1: _ton_api["default"], 2: _lite_api["default"] }, config: _tonLiteClientTest1Config["default"], wssProxies: { 861606190: 'wss://ton-ws.madelineproto.xyz/testnetDebug', 1137658550: 'wss://ton-ws.madelineproto.xyz/testnet' } }); var config = settings.config; delete settings.config; this.settings = settings; this.TLObjects = new _objects["default"](settings['schemes']); this.TLParser = new _parser["default"](this.TLObjects, { typeConversion: { 'liteServer.AccountId': function liteServerAccountId(data) { return _this.unpackAccountId(data); } } }); var stream = new _stream["default"](); // This is also a TL serializer test config['_'] = 'liteclient.config.global'; config['validator']['init_block'] = config['validator']['init_block'] || config['validator']['zero_state']; config = this.TLParser.serialize(stream, config); config.pos = 0; this.config = this.TLParser.deserialize(config); this.crypto = new _crypto["default"](this.TLParser); } /** * Unpack account ID to a liteServer.accountId object * @param {string} data Account ID */ (0, _createClass2["default"])(Lite, [{ key: "unpackAccountId", value: function unpackAccountId(data) { data = (0, _tools.atobInt8)(data.replace('-', '+').replace('_', '/')); var crc = (0, _crypto2.crc16)(data.subarray(0, 34)); if (!(0, _tools.bufferViewEqual)(crc, data.subarray(34))) { throw new Error('Invalid account ID provided, crc16 invalid!'); } var result = { _: 'liteServer.accountId', flags: data[0] }; if ((result.flags & 0x3f) != 0x11) { throw new Error('Invalid account ID, wrong flags'); } result.testnet = result.flags & 0x80; result.bounceable = !(result.flags & 0x40); result.workchain = data[1] > 0x7F ? -(data[1] - 0xFF) : data[1]; result.id = new Uint32Array(data.slice(2, 34).buffer); return result; } /** * Deserialize bag of cells * @param {Uint8Array} cell Serialized cell data */ }, { key: "slice", value: function slice(cell) { if (!cell.byteLength) { return {}; } // This should be all done automatically by the TL parser console.log(cell); var stream = new _bitstream["default"](cell.buffer); var crc = stream.readBits(32); if (crc !== 3052313714) { throw new Error("Invalid BOC constructor ".concat(crc)); } var result = { _: 'serialized_boc', has_idx: stream.readBits(1), has_crc32c: stream.readBits(1), has_cache_bits: stream.readBits(1), flags: stream.readBits(2), size: stream.readBits(3), off_bytes: stream.readBits(8) }; var size = result.size * 8; result.cell_count = stream.readBits(size); result.roots = stream.readBits(size); result.absent = stream.readBits(size); result.tot_cells_size = stream.readBits(result.off_bytes * 8); result.root_list = stream.readBits(result.roots * size); if (result.has_idx) { result.index = stream.readBits(result.cell_count * result.off_bytes * 8); } result.cell_data = stream.readBits(result.tot_cells_size * 8, false); if (result.has_crc32c) { result.crc32c = stream.readBits(32); } stream = new _bitstream["default"](result.cell_data.buffer); result.cellsRaw = []; result.cells = []; for (var x = 0; x < result.cell_count; x++) { // Will optimize later result.cellsRaw.push(this.deserializeCell(stream, size)); result.cells.push(new _bitstream["default"](result.cellsRaw[x].data.buffer)); } for (var _x = 0; _x < result.cell_count; _x++) { for (var ref in result.cellsRaw[_x].refs) { result.cells[_x].pushRef(result.cells[ref]); } } result.root = result.cells[0]; return result; } /** * Deserialize cell * @param {BitStream} stream Bitstream of cells * @param {number} size Ref size */ }, { key: "deserializeCell", value: function deserializeCell(stream, size) { // Approximated TL-B schema // cell$_ flags:(## 2) level:(## 1) hash:(## 1) exotic:(## 1) absent:(## 1) refCount:(## 2) var result = {}; result.flags = stream.readBits(2); result.level = stream.readBits(1); result.hash = stream.readBits(1); result.exotic = stream.readBits(1); result.absent = stream.readBits(1); result.refCount = stream.readBits(2); if (result.absent) { throw new Error("Can't deserialize absent cell!"); } result.length = stream.readBits(7); result.lengthHasBits = stream.readBits(1); result.data = stream.readBits((result.length + result.lengthHasBits) * 8); if (result.lengthHasBits) { var idx = result.data.byteLength - 1; for (var x = 0; x < 6; x++) { if (result.data[idx] & 1 << x) { result.data[idx] &= ~(1 << x); break; } } } result.refs = []; for (var _x2 = 0; _x2 < result.refCount; _x2++) { result.refs.push(stream.readBits(length)); } return result; } /** * Connect to all liteservers */ }, { key: "connect", value: function () { var _connect = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee() { var _this2 = this; var promises, _loop, key; return _regenerator["default"].wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: promises = []; _loop = function _loop(key) { var uri = _this2.settings['wssProxies'][_this2.config['liteservers'][key]['ip']]; var connection = new _adnlConnection["default"](_this2.getTL(), _this2.config['liteservers'][key]['id'], uri); promises.push(connection.connect().then(function () { return _this2.connections.push(connection); })); }; for (key in this.config['liteservers']) { _loop(key); } _context.next = 5; return Promise.all(promises); case 5: _context.next = 7; return this.getVersion(); case 7: console.log("Server version is ".concat(this.server.version >> 8, ".").concat(this.server.version & 0xFF, ", capabilities ").concat(this.server.capabilities)); if (!this.server.ok) { console.error("Server version is too old (at least ".concat(this.min_ls_version >> 8, ".").concat(this.min_ls_version & 0xFF, " with capabilities ").concat(this.min_ls_capabilities, " required), some queries are unavailable!")); } console.log("Server time is ".concat(this.serverTime)); // Get masterchain info _context.next = 12; return this.getMasterchainInfo(); case 12: case "end": return _context.stop(); } } }, _callee, this); })); function connect() { return _connect.apply(this, arguments); } return connect; }() /** * Get masterchain info (equivalent to `last`) * @returns {Object} liteServer.masterchainInfoExt or liteServer.masterchainInfo, depending on liteserver version */ }, { key: "last", value: function last() { return this.getMasterchainInfo(); } /** * Get masterchain info (equivalent to `last`) * @returns {Object} liteServer.masterchainInfoExt or liteServer.masterchainInfo, depending on liteserver version */ }, { key: "getMasterchainInfo", value: function () { var _getMasterchainInfo = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee2() { var mode, info; return _regenerator["default"].wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: mode = this.server.capabilities[0] & 2 ? 0 : -1; _context2.next = 3; return this.methodCall(mode < 0 ? 'liteServer.getMasterchainInfo' : 'liteServer.getMasterchainInfoExt', { mode: mode }); case 3: info = _context2.sent; this._parseMasterchainInfo(info); // Reduce clutter for abstraction methods return _context2.abrupt("return", info); case 6: case "end": return _context2.stop(); } } }, _callee2, this); })); function getMasterchainInfo() { return _getMasterchainInfo.apply(this, arguments); } return getMasterchainInfo; }() /** * Request block by ID * @param {Object} id Block ID */ }, { key: "requestBlock", value: function () { var _requestBlock = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee3(id) { var block; return _regenerator["default"].wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _context3.next = 2; return this.methodCall('liteServer.getBlock', { id: id }); case 2: block = _context3.sent; if ((0, _deepEqual["default"])(block.id, id)) { _context3.next = 6; break; } console.error(block, id); throw new Error('Got wrong block!'); case 6: return _context3.abrupt("return", block); case 7: case "end": return _context3.stop(); } } }, _callee3, this); })); function requestBlock(_x3) { return _requestBlock.apply(this, arguments); } return requestBlock; }() /** * Get server version * @returns {Object} */ }, { key: "getVersion", value: function () { var _getVersion = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee4() { var server; return _regenerator["default"].wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _context4.next = 2; return this.methodCall('liteServer.getVersion'); case 2: server = _context4.sent; this._setServerVersion(server); this._setServerTime(server.now); return _context4.abrupt("return", server); case 6: case "end": return _context4.stop(); } } }, _callee4, this); })); function getVersion() { return _getVersion.apply(this, arguments); } return getVersion; }() /** * Get server time * @returns {Object} */ }, { key: "getTime", value: function () { var _getTime = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee5() { var time; return _regenerator["default"].wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return this.methodCall('liteServer.getTime'); case 2: time = _context5.sent; this._setServerTime(time.now); return _context5.abrupt("return", time); case 5: case "end": return _context5.stop(); } } }, _callee5, this); })); function getTime() { return _getTime.apply(this, arguments); } return getTime; }() // Parser functions }, { key: "_setServerVersion", value: function _setServerVersion(server) { this.server = server; this.server.ok = server.version >= this.min_ls_version && !(~server.capabilities[0] & this.min_ls_capabilities); } }, { key: "_setServerTime", value: function _setServerTime(time) { this.serverTime = time; this.gotServerTimeAt = Date.now() / 1000 | 0; } }, { key: "_parseMasterchainInfo", value: function _parseMasterchainInfo(info) { var last = info.last; var zero = info.init; var last_utime = info.last_utime || 0; if (info['_'] === 'liteServer.masterchainInfoExt') { this._setServerVersion(info); this._setServerTime(info.now); if (last_utime > this.serverTime) { console.error("server claims to have a masterchain block", last, "created at ".concat(last_utime, " (").concat(last_utime - this.serverTime, " seconds in the future)")); } else if (last_utime < this.serverTime - 60) { console.error("server appears to be out of sync: its newest masterchain block is", last, "created at ".concat(last_utime, " (").concat(server_now - last_utime, " seconds ago according to the server's clock)")); } else if (last_utime < this.gotServerTimeAt - 60) { console.error("either the server is out of sync, or the local clock is set incorrectly: the newest masterchain block known to server is", last, "created at ".concat(last_utime, " (").concat(server_now - this.gotServerTimeAt, " seconds ago according to the local clock)")); } } var myZeroState; if (this.zeroState) { myZeroState = this.zeroState; } else { myZeroState = this.config.validator.zero_state; myZeroState._ = 'tonNode.zeroStateIdExt'; delete myZeroState.shard; delete myZeroState.seqno; } if (!(0, _deepEqual["default"])(zero, myZeroState)) { console.log(this.config); console.error("Zerostate changed: should be", myZeroState, "is", zero); throw new Error("Zerostate changed!"); } if (!this.zeroState) { this.zeroState = zero; /*zero._ = 'tonNode.blockIdExt' zero.seqno = 0 zero.shard = [0, -2147483648]*/ //this._registerBlockId(zero) console.log("Zerostate OK!"); } //this._registerBlockId(last) if (!this.lastMasterchainId) { this.lastMasterchainId = last; this.requestBlock(last); } else if (this.lastMasterchainId.seqno < last.seqno) { this.lastMasterchainId = last; } console.log("Last masterchain block ID known to server is ", last, last_utime ? "created at ".concat(last_utime) : ''); } // Unused for now }, { key: "_registerBlockId", value: function _registerBlockId(id) { for (var block in this.knownBlockIds) { if ((0, _deepEqual["default"])(block, id)) { return; } } this.knownBlockIds.push(id); } }, { key: "_registerMasterchainBlock", value: function () { var _registerMasterchainBlock2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/ _regenerator["default"].mark(function _callee6(id, data) { var hash; return _regenerator["default"].wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: _context6.t0 = Uint32Array; _context6.next = 3; return this.crypto.sha256(data); case 3: _context6.t1 = _context6.sent; hash = new _context6.t0(_context6.t1); if ((0, _tools.bufferViewEqual)(id.file_hash, hash)) { _context6.next = 8; break; } console.error("File hash mismatch for block ", id, "expected ".concat(id.file_hash, ", got ").concat(hash)); throw new Error("File hash mismatch!"); case 8: case "end": return _context6.stop(); } } }, _callee6, this); })); function _registerMasterchainBlock(_x4, _x5) { return _registerMasterchainBlock2.apply(this, arguments); } return _registerMasterchainBlock; }() /** * Call liteserver method * @param {string} method Liteserver method name * @param {Object} args Arguments */ }, { key: "methodCall", value: function methodCall(method) { var args = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; args['_'] = method; args['id'] = args['id'] || this.lastMasterchainId; var data; data = this.TLParser.serialize(new _stream["default"](), args).bBuf; data = this.TLParser.serialize(new _stream["default"](), { _: 'liteServer.query', data: data }).bBuf; return this.connections[(0, _random.fastRandomInt)(this.connections.length)].query(data); } }, { key: "getTL", value: function getTL() { return this.TLParser; } }]); return Lite; }(); var _default = Lite; exports["default"] = _default;