madeline-ton
Version:
Pure JS client-side implementation of the Telegram TON blockchain protocol
643 lines (524 loc) • 20.9 kB
JavaScript
"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;