UNPKG

@pokt-network/pocket-js

Version:

Pocket-js core package with the main functionalities to interact with the Pocket Network.

530 lines 32.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pocket = exports.HTTPMethod = void 0; var config_1 = require("./config"); var models_1 = require("./rpc/models"); var utils_1 = require("./utils"); var session_manager_1 = require("./session/session-manager"); var rpc_1 = require("./rpc"); var keybase_1 = require("./keybase/keybase"); var models_2 = require("./keybase/models"); var _1 = require("./"); var consensus_relay_response_1 = require("./rpc/models/output/consensus-relay-response"); var consensus_node_1 = require("./rpc/models/consensus-node"); var request_hash_1 = require("./rpc/models/input/request-hash"); var challenge_request_1 = require("./rpc/models/input/challenge-request"); var challenge_response_1 = require("./rpc/models/output/challenge-response"); var relay_meta_1 = require("./rpc/models/input/relay-meta"); var profile_result_1 = require("./utils/models/profile-result"); var no_op_profiler_1 = require("./utils/no-op-profiler"); var proto_tx_decoder_1 = require("./transactions/factory/proto-tx-decoder"); /** * * HTTPMethod enum with the possible Staking status values */ var HTTPMethod; (function (HTTPMethod) { HTTPMethod["POST"] = "POST"; HTTPMethod["GET"] = "GET"; HTTPMethod["DELETE"] = "DELETE"; HTTPMethod["NA"] = ""; })(HTTPMethod = exports.HTTPMethod || (exports.HTTPMethod = {})); /** * * * @class Pocket */ var Pocket = /** @class */ (function () { /** * Creates an instance of Pocket. * @param {URL} dispatchers - Array holding the initial dispatcher url(s). * @param {IRPCProvider} rpcProvider - Provider which will be used to reach out to the Pocket Core RPC interface. * @param {Configuration} configuration - Configuration object. * @param {IKVStore} store - Save data using a Key/Value relationship. This object save information in memory. * @param {BaseProfiler} profiler - BaseProfiler implementation for metrics, defaults to an empty NoOpProfiler. * @memberof Pocket */ function Pocket(dispatchers, rpcProvider, configuration, store, profiler) { if (configuration === void 0) { configuration = new config_1.Configuration(); } if (store === void 0) { store = new _1.InMemoryKVStore(); } if (profiler === void 0) { profiler = new no_op_profiler_1.NoOpProfiler(); } this.configuration = configuration; this.sessionManager = new session_manager_1.SessionManager(dispatchers, configuration, store); this.keybase = new keybase_1.Keybase(store); this.profiler = profiler; if (rpcProvider !== undefined) { this.innerRpc = new rpc_1.RPC(rpcProvider); } } /** * Returns the Session Manager's routing table dispatcher's count * @returns {Number} - Dispatcher's count. * @memberof Pocket */ Pocket.prototype.getDispatchersCount = function () { return this.sessionManager.getDispatchersCount(); }; /** * Creates a new instance of RPC if you set an IRPCProvider or return the previous existing instance * @param {IRPCProvider} rpcProvider - Provider which will be used to reach out to the Pocket Core RPC interface. * @returns {RPC} - A RPC object. * @memberof Pocket */ Pocket.prototype.rpc = function (rpcProvider) { if (rpcProvider !== undefined) { this.innerRpc = new rpc_1.RPC(rpcProvider); } if (this.innerRpc !== undefined) { return this.innerRpc; } }; /** * * Sends a Relay Request to multiple nodes for manual consensus * @param {string} data - string holding the json rpc call. * @param {string} blockchain - Blockchain hash. * @param {PocketAAT} pocketAAT - Pocket Authentication Token. * @param {Configuration} configuration - Pocket configuration object. * @param {RelayHeaders} headers - (Optional) An object holding the HTTP Headers. * @param {HTTPMethod} method - (Optional) HTTP method for REST API calls. * @param {string} path - (Optional) REST API path. * @param {Node} node - (Optional) Session node which will receive the Relay. * @returns {ConsensusRelayResponse | ChallengeResponse | Error} - A Consensus Relay Response object, Challenge response or error. * @memberof Pocket */ Pocket.prototype.sendConsensusRelay = function (data, blockchain, pocketAAT, configuration, headers, method, path, node) { if (configuration === void 0) { configuration = this.configuration; } if (method === void 0) { method = HTTPMethod.NA; } if (path === void 0) { path = ""; } return __awaiter(this, void 0, void 0, function () { var consensusNodes, firstResponse, index, consensusNodeResponse, consensusRelayResponse, challengeRequest, challengeResponseOrError, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: consensusNodes = []; _a.label = 1; case 1: _a.trys.push([1, 11, , 12]); // Checks if max consensus nodes count is 0, meaning it wasn't configured for local concensus. if (this.configuration.consensusNodeCount === 0) { return [2 /*return*/, new rpc_1.RpcError("NA", "Failed to send a relay with local consensus, configuration consensusNodeCount is 0")]; } index = 0; _a.label = 2; case 2: if (!(index < this.configuration.consensusNodeCount)) return [3 /*break*/, 5]; return [4 /*yield*/, this.sendRelay(data, blockchain, pocketAAT, configuration, headers || undefined, method, path, node, true) // Check if ConsensusNode type ]; case 3: consensusNodeResponse = _a.sent(); // Check if ConsensusNode type if (utils_1.typeGuard(consensusNodeResponse, consensus_node_1.ConsensusNode)) { // Save the first response if (index === 0) { firstResponse = consensusNodeResponse.relayResponse; } consensusNodes.push(consensusNodeResponse); } else if (utils_1.typeGuard(consensusNodeResponse, Error)) { return [2 /*return*/, consensusNodeResponse]; } _a.label = 4; case 4: index++; return [3 /*break*/, 2]; case 5: // Check consensus nodes length if (consensusNodes.length === 0 || firstResponse === undefined) { return [2 /*return*/, new rpc_1.RpcError("NA", "Failed to send a relay with local consensus, no responses.")]; } consensusRelayResponse = new consensus_relay_response_1.ConsensusRelayResponse(firstResponse.signature, firstResponse.payload, consensusNodes); if (!consensusRelayResponse.consensusResult) return [3 /*break*/, 6]; return [2 /*return*/, consensusRelayResponse]; case 6: if (!configuration.acceptDisputedResponses) return [3 /*break*/, 7]; return [2 /*return*/, consensusRelayResponse]; case 7: if (!(consensusRelayResponse.majorityResponse !== undefined && consensusRelayResponse.minorityResponse !== undefined)) return [3 /*break*/, 9]; challengeRequest = new challenge_request_1.ChallengeRequest(consensusRelayResponse.majorityResponse, consensusRelayResponse.minorityResponse); return [4 /*yield*/, this.rpc().query.requestChallenge(challengeRequest) // Return a challenge response ]; case 8: challengeResponseOrError = _a.sent(); // Return a challenge response if (utils_1.typeGuard(challengeResponseOrError, challenge_response_1.ChallengeResponse)) { return [2 /*return*/, challengeResponseOrError]; } return [2 /*return*/, challengeResponseOrError]; case 9: return [2 /*return*/, new rpc_1.RpcError("NA", "Failed to send a consensus relay due to false consensus result, not accepting disputed responses without proper majority and minority responses.")]; case 10: return [3 /*break*/, 12]; case 11: err_1 = _a.sent(); return [2 /*return*/, rpc_1.RpcError.fromError(err_1)]; case 12: return [2 /*return*/]; } }); }); }; /** * * Sends a Relay Request * @param {string} data - string holding the json rpc call. * @param {string} blockchain - Blockchain hash. * @param {PocketAAT} pocketAAT - Pocket Authentication Token. * @param {Configuration} configuration - Pocket configuration object. * @param {RelayHeaders} headers - (Optional) An object holding the HTTP Headers. * @param {HTTPMethod} method - (Optional) HTTP method for REST API calls. * @param {string} path - (Optional) REST API path. * @param {Node} node - (Optional) Session node which will receive the Relay. * @param {boolean} consensusEnabled - (Optional) True or false if the relay will be sent to multiple nodes for consensus, default is false. * @returns {RelayResponse} - A Relay Response object. * @memberof Pocket */ Pocket.prototype.sendRelay = function (data, blockchain, pocketAAT, configuration, headers, method, path, node, consensusEnabled, requestID) { if (configuration === void 0) { configuration = this.configuration; } if (method === void 0) { method = HTTPMethod.NA; } if (path === void 0) { path = ""; } if (consensusEnabled === void 0) { consensusEnabled = false; } if (requestID === void 0) { requestID = ""; } return __awaiter(this, void 0, void 0, function () { var profileResults, functionName, profileResult, currentSessionOrError, currentSession, serviceNode, serviceNodeOrError, serviceNodeOrError, serviceProvider, rpc, relayPayload, clientAddressHex, isUnlocked, relayMeta, requestHash, entropy, proofBytes, signatureOrError, signature, signatureHex, relayProof, relay, result, rpcError, sessionRefreshed, retryIndex, newSessionOrError, newSession, newSessionOrError, newSession, refreshedRelay, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 25, , 26]); profileResults = []; functionName = "send_relay"; profileResult = new profile_result_1.ProfileResult("get_current_session"); return [4 /*yield*/, this.sessionManager.getCurrentSession(pocketAAT, blockchain, configuration)]; case 1: currentSessionOrError = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (utils_1.typeGuard(currentSessionOrError, Error)) { return [2 /*return*/, rpc_1.RpcError.fromError(currentSessionOrError)]; } currentSession = currentSessionOrError; serviceNode = void 0; // Check if consensus relay is enabled if (consensusEnabled) { if (this.configuration.consensusNodeCount > currentSession.sessionNodes.length) { return [2 /*return*/, new rpc_1.RpcError("NA", "Failed to send a consensus relay, number of max consensus nodes is higher thant the actual nodes in current session")]; } // Profiler profileResult = new profile_result_1.ProfileResult("get_unique_session_node"); serviceNodeOrError = currentSession.getUniqueSessionNode(); profileResult.save(); profileResults.push(profileResult); if (utils_1.typeGuard(serviceNodeOrError, Error)) { return [2 /*return*/, rpc_1.RpcError.fromError(serviceNodeOrError)]; } serviceNode = serviceNodeOrError; } else { // Provide a random service node from the session if (node !== undefined) { if (currentSession.isNodeInSession(node)) { serviceNode = node; } else { return [2 /*return*/, new rpc_1.RpcError("NA", "Provided Node is not part of the current session for this application, check your PocketAAT")]; } } else { // Profiler profileResult = new profile_result_1.ProfileResult("get_session_node"); serviceNodeOrError = currentSession.getSessionNode(); profileResult.save(); profileResults.push(profileResult); if (utils_1.typeGuard(serviceNodeOrError, Error)) { return [2 /*return*/, rpc_1.RpcError.fromError(serviceNodeOrError)]; } serviceNode = serviceNodeOrError; } } // Final Service Node check if (serviceNode === undefined) { return [2 /*return*/, new rpc_1.RpcError("NA", "Could not determine a Service Node to submit this relay")]; } serviceProvider = new rpc_1.HttpRpcProvider(serviceNode.serviceURL); rpc = new rpc_1.RPC(serviceProvider); relayPayload = new models_1.RelayPayload(data, method, path, headers || null); // Profiler profileResult = new profile_result_1.ProfileResult("address_from_public_key"); clientAddressHex = utils_1.addressFromPublickey(Buffer.from(pocketAAT.clientPublicKey, 'hex')).toString("hex"); profileResult.save(); profileResults.push(profileResult); // Profiler profileResult = new profile_result_1.ProfileResult("keybase_is_unlocked"); return [4 /*yield*/, this.keybase.isUnlocked(clientAddressHex)]; case 2: isUnlocked = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (!isUnlocked) { return [2 /*return*/, new rpc_1.RpcError("NA", "Client account " + clientAddressHex + " for this AAT is not unlocked")]; } relayMeta = new relay_meta_1.RelayMeta(currentSession.sessionHeader.sessionBlockHeight); requestHash = new request_hash_1.RequestHash(relayPayload, relayMeta); entropy = BigInt(Math.floor(Math.random() * 99999999999999999)); // Profiler profileResult = new profile_result_1.ProfileResult("relay_proof_bytes"); proofBytes = models_1.RelayProof.bytes(entropy, currentSession.sessionHeader.sessionBlockHeight, serviceNode.publicKey, blockchain, pocketAAT, requestHash); profileResult.save(); profileResults.push(profileResult); // Profiler profileResult = new profile_result_1.ProfileResult("sign_with_unlocked_account"); return [4 /*yield*/, this.keybase.signWithUnlockedAccount(clientAddressHex, proofBytes)]; case 3: signatureOrError = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (!utils_1.typeGuard(signatureOrError, Error)) return [3 /*break*/, 5]; return [4 /*yield*/, this.profiler.flushResults({ requestID: requestID, blockchain: blockchain }, functionName, profileResults)]; case 4: _a.sent(); return [2 /*return*/, new rpc_1.RpcError("NA", "Error signing Relay proof: " + signatureOrError.message)]; case 5: signature = signatureOrError; signatureHex = signature.toString("hex"); relayProof = new models_1.RelayProof(entropy, currentSession.sessionHeader.sessionBlockHeight, serviceNode.publicKey, blockchain, pocketAAT, signatureHex, requestHash); relay = new models_1.RelayRequest(relayPayload, relayMeta, relayProof); // Profiler profileResult = new profile_result_1.ProfileResult("rpc_client_relay"); return [4 /*yield*/, rpc.client.relay(relay, configuration.validateRelayResponses, configuration.requestTimeOut, configuration.rejectSelfSignedCertificates)]; case 6: result = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (!utils_1.typeGuard(result, rpc_1.RpcError)) return [3 /*break*/, 21]; rpcError = result; if (!(rpcError.code === "60" || // InvalidBlockHeightError = errors.New("the block height passed is invalid") rpcError.code === "75" || // OutOfSyncRequestError = errors.New("the request block height is out of sync with the current block height") rpcError.code === "14")) return [3 /*break*/, 18]; // Profiler profileResult = new profile_result_1.ProfileResult("destroy_session"); // Remove outdated session this.sessionManager.destroySession(pocketAAT, blockchain); profileResult.save(); profileResults.push(profileResult); sessionRefreshed = false; retryIndex = 0; _a.label = 7; case 7: if (!(retryIndex < configuration.maxSessionRefreshRetries)) return [3 /*break*/, 12]; if (!(rpcError.session !== undefined)) return [3 /*break*/, 9]; // Profiler profileResult = new profile_result_1.ProfileResult("update_current_session"); return [4 /*yield*/, this.sessionManager.updateCurrentSession(rpcError.session, pocketAAT, blockchain, configuration)]; case 8: newSessionOrError = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (utils_1.typeGuard(newSessionOrError, Error)) { // If error or same session, don't even retry relay return [3 /*break*/, 11]; } else if (utils_1.typeGuard(newSessionOrError, models_1.Session)) { newSession = newSessionOrError; if (newSession.sessionHeader.sessionBlockHeight === currentSession.sessionHeader.sessionBlockHeight) { // If we get the same session skip this attempt return [3 /*break*/, 11]; } currentSession = newSession; } sessionRefreshed = true; return [3 /*break*/, 12]; case 9: profileResult = new profile_result_1.ProfileResult("request_new_session"); return [4 /*yield*/, this.sessionManager.requestNewSession(pocketAAT, blockchain, configuration)]; case 10: newSessionOrError = _a.sent(); profileResult.save(); profileResults.push(profileResult); if (utils_1.typeGuard(newSessionOrError, Error)) { // If error or same session, don't even retry relay return [3 /*break*/, 11]; } else if (utils_1.typeGuard(newSessionOrError, models_1.Session)) { newSession = newSessionOrError; if (newSession.sessionHeader.sessionBlockHeight === currentSession.sessionHeader.sessionBlockHeight) { // If we get the same session skip this attempt return [3 /*break*/, 11]; } currentSession = newSession; } sessionRefreshed = true; return [3 /*break*/, 12]; case 11: retryIndex++; return [3 /*break*/, 7]; case 12: if (!sessionRefreshed) return [3 /*break*/, 15]; // Profiler profileResult = new profile_result_1.ProfileResult("session_refreshed_send_relay"); return [4 /*yield*/, this.sendRelay(data, blockchain, pocketAAT, configuration, headers, method, path, node, consensusEnabled, requestID)]; case 13: refreshedRelay = _a.sent(); profileResult.save(); profileResults.push(profileResult); return [4 /*yield*/, this.profiler.flushResults({ requestID: requestID, blockchain: blockchain }, functionName, profileResults)]; case 14: _a.sent(); return [2 /*return*/, refreshedRelay]; case 15: return [4 /*yield*/, this.profiler.flushResults({ requestID: requestID, blockchain: blockchain }, functionName, profileResults)]; case 16: _a.sent(); return [2 /*return*/, new rpc_1.RpcError(rpcError.code, rpcError.message, undefined, serviceNode.publicKey)]; case 17: return [3 /*break*/, 20]; case 18: return [4 /*yield*/, this.profiler.flushResults({ requestID: requestID, blockchain: blockchain }, functionName, profileResults)]; case 19: _a.sent(); return [2 /*return*/, new rpc_1.RpcError(rpcError.code, rpcError.message, undefined, serviceNode.publicKey)]; case 20: return [3 /*break*/, 24]; case 21: if (!(consensusEnabled && utils_1.typeGuard(result, models_1.RelayResponse))) return [3 /*break*/, 22]; // Handle consensus if (currentSession.sessionNodes.indexOf(serviceNode)) { currentSession.sessionNodes[currentSession.sessionNodes.indexOf(serviceNode)].alreadyInConsensus = true; } return [2 /*return*/, new consensus_node_1.ConsensusNode(serviceNode, false, result)]; case 22: // Profiler profileResult = new profile_result_1.ProfileResult("add_new_dispatcher"); // Add the used session node to the routing table dispatcher's list this.sessionManager.addNewDispatcher(serviceNode); profileResult.save(); return [4 /*yield*/, this.profiler.flushResults({ requestID: requestID, blockchain: blockchain }, functionName, profileResults)]; case 23: _a.sent(); return [2 /*return*/, result]; case 24: return [3 /*break*/, 26]; case 25: error_1 = _a.sent(); return [2 /*return*/, rpc_1.RpcError.fromError(error_1)]; case 26: return [2 /*return*/]; } }); }); }; /** * Creates an ITransactionSender given a private key * @param {Buffer | string} privateKey * @returns {ITransactionSender} - Interface with all the possible MsgTypes in a Pocket Network transaction and a function to submit the transaction to the network. * @memberof Pocket */ Pocket.prototype.withPrivateKey = function (privateKey) { try { var privKeyBuffer = utils_1.typeGuard(privateKey, Buffer) ? privateKey : Buffer.from(privateKey, 'hex'); if (!utils_1.validatePrivateKey(privKeyBuffer)) { throw new Error("Invalid private key"); } var pubKey = utils_1.publicKeyFromPrivate(privKeyBuffer); var unlockedAccount = new models_2.UnlockedAccount(new models_2.Account(pubKey, ''), privKeyBuffer); return new _1.TransactionSender(this, unlockedAccount); } catch (err) { return err; } }; /** * Creates an ITransactionSender given an already imported account into this instanc keybase * @param {Buffer | string} address - address of the account * @param {string} passphrase - passphrase for the account * @returns {ITransactionSender} - Interface with all the possible MsgTypes in a Pocket Network transaction and a function to submit the transaction to the network. * @memberof Pocket */ Pocket.prototype.withImportedAccount = function (address, passphrase) { return __awaiter(this, void 0, void 0, function () { var unlockedAccountOrError; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.keybase.getUnlockedAccount(utils_1.typeGuard(address, "string") ? address : address.toString("hex"), passphrase)]; case 1: unlockedAccountOrError = _a.sent(); if (utils_1.typeGuard(unlockedAccountOrError, Error)) { return [2 /*return*/, unlockedAccountOrError]; } else { return [2 /*return*/, new _1.TransactionSender(this, unlockedAccountOrError)]; } return [2 /*return*/]; } }); }); }; /** * Creates an ITransactionSender given a {TransactionSigner} function * @param {TransactionSigner} txSigner - Function which will sign the transaction bytes * @returns {ITransactionSender} - Interface with all the possible MsgTypes in a Pocket Network transaction and a function to submit the transaction to the network. * @memberof Pocket */ Pocket.prototype.withTxSigner = function (txSigner) { try { return new _1.TransactionSender(this, undefined, txSigner); } catch (err) { return err; } }; Pocket.prototype.withProtoTxDecoder = function () { try { return new proto_tx_decoder_1.ProtoTxDecoder(); } catch (err) { return err; } }; return Pocket; }()); exports.Pocket = Pocket; __exportStar(require("@pokt-network/aat-js"), exports); //# sourceMappingURL=pocket.js.map