UNPKG

postchain-client

Version:

Client library for accessing a Postchain node through REST.

391 lines 22.9 kB
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()); }); }; import { randomBytes } from "crypto"; import cloneDeep from "lodash/cloneDeep"; import { logger } from "../.."; import { toBuffer, toString, toQueryObjectGTV } from "../formatter"; import { decodeValue, encodeValue } from "../gtx/serialization"; import { handleGetResponse, handlePostResponse, } from "../restclient/restclient"; import { requestWithFailoverStrategy, sleep, } from "../restclient/restclientutil"; import * as gtxTool from "../gtx/gtx"; import { TxRejectedError } from "../restclient/errors"; import { callbackPromiseBuilder, formatBlockInfoResponse, formatTransactionInfoResponse, getClientConfigFromSettings, getGTXFromBufferOrTransactionOrOperation, getSerializedGTX, handlePostResponsePromisified, isKeyPair, } from "./utils"; import { Web3PromiEvent } from "../promiEvent/promiEvents"; import { NumberOfSignersAndSignaturesException } from "../gtx/errors"; import { GetTransactionRidException } from "./errors"; import { Method } from "../restclient/enums"; import { ResponseStatus } from "./enums"; import { isBlockIdentifierValid } from "./validation/blockIdentifier"; import { isNetworkSettingValid } from "./validation/networkSettings"; import { isSignMethodValid } from "./validation/signMethod"; import { isTxRidValid } from "./validation/txRid"; export function createClient(settings) { return __awaiter(this, void 0, void 0, function* () { isNetworkSettingValid(settings, { throwOnError: true }); return { config: yield getClientConfigFromSettings(settings), query(nameOrQueryObject, args, callback) { return __awaiter(this, void 0, void 0, function* () { let _name, _args; if (typeof nameOrQueryObject === "string") { _name = nameOrQueryObject; _args = args; } else { _name = nameOrQueryObject === null || nameOrQueryObject === void 0 ? void 0 : nameOrQueryObject.name; _args = nameOrQueryObject === null || nameOrQueryObject === void 0 ? void 0 : nameOrQueryObject.args; } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.POST, `query_gtv/${this.config.blockchainRid}`, this.config, encodeValue(toQueryObjectGTV(_name, _args))); return new Promise((resolve, reject) => { handlePostResponse(error, statusCode, rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, signTransaction(transaction, signMethod, callback) { return __awaiter(this, void 0, void 0, function* () { logger.debug(`Signing transaction with ${!isKeyPair(signMethod) ? "signature provider containing " : ""}pubKey: ${toString(signMethod.pubKey)}`); const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRid); try { const signedTx = yield (isKeyPair(signMethod) ? gtxTool.sign(gtx, signMethod.privKey, signMethod.pubKey) : gtxTool.sign(gtx, signMethod)); const gtxBytes = getSerializedGTX(signedTx); if (typeof callback === "function") { callback(null, gtxBytes); } return gtxBytes; } catch (error) { if (typeof callback === "function") { callback(error, null); } throw new Error(error); } }); }, sendTransaction(transaction, doStatusPolling = true, callback = undefined) { const promiEvent = new Web3PromiEvent((resolve, reject) => __awaiter(this, void 0, void 0, function* () { var _a; try { const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRid); if (gtx.signers.length !== ((_a = gtx.signatures) === null || _a === void 0 ? void 0 : _a.length)) { reject(new NumberOfSignersAndSignaturesException()); } const gtxBytes = getSerializedGTX(gtx); const transactionObject = { tx: gtxBytes.toString("hex"), }; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.POST, `tx/${this.config.blockchainRid}`, this.config, transactionObject); const transactionRid = gtxTool.getDigestToSign(gtx); try { yield handlePostResponsePromisified(error, statusCode, rspBody); if (typeof callback === "function") { callback(null, { status: ResponseStatus.Waiting, statusCode, transactionRid: transactionRid, }); } } catch (_error) { if (typeof callback === "function") { callback(_error, null); } return reject(_error); } const transactionReceipt = { status: ResponseStatus.Waiting, statusCode: statusCode, transactionRid: transactionRid, }; promiEvent.emit("sent", transactionReceipt); if (doStatusPolling === false) { return resolve(transactionReceipt); } const awaitConfirmation = (txRID) => __awaiter(this, void 0, void 0, function* () { var _b; let lastKnownResult; for (let i = 0; i < this.config.statusPollCount; i++) { lastKnownResult = yield this.getTransactionStatus(txRID); if (lastKnownResult.status === ResponseStatus.Confirmed) { return ResponseStatus.Confirmed; } else if (lastKnownResult.status === ResponseStatus.Rejected) { throw new TxRejectedError((_b = lastKnownResult.rejectReason) !== null && _b !== void 0 ? _b : ""); } yield sleep(this.config.statusPollInterval); } // TS issue. This could be fixed by implementing new retry strategy // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-expect-error return lastKnownResult.status; }); const confirmationStatus = yield awaitConfirmation(gtxTool.getDigestToSign(gtx)); resolve({ status: confirmationStatus, statusCode: statusCode, transactionRid: transactionRid, }); } catch (error) { reject(error); } })); return promiEvent; }, signAndSendUniqueTransaction(transactionOrOperation, signMethod, doStatusPolling = true, callback = undefined) { isSignMethodValid(signMethod, { throwOnError: true }); const promiEvent = new Web3PromiEvent((resolve, reject) => { const transaction = "name" in transactionOrOperation ? { operations: [transactionOrOperation], signers: [signMethod.pubKey], } : transactionOrOperation; const hasNop = transaction.operations.some((operation) => { return operation.name === "nop"; }); const transactionWithNop = hasNop ? transaction : this.addNop(transaction); this.signTransaction(transactionWithNop, signMethod) .then((signedTx) => { const sendTransactionPromiEvent = this.sendTransaction(signedTx, doStatusPolling, callback); sendTransactionPromiEvent.on("sent", (receipt) => { promiEvent.emit("sent", receipt); }); resolve(sendTransactionPromiEvent); }) .catch((error) => { reject(error); }); }); return promiEvent; }, getTransaction(transactionRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { isTxRidValid(transactionRid, { throwOnError: true }); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `tx/${this.config.blockchainRid}/${transactionRid.toString("hex")}`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 ? toBuffer(rspBody.tx) : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getTransactionStatus(transactionRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { isTxRidValid(transactionRid, { throwOnError: true }); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `tx/${this.config.blockchainRid}/${transactionRid.toString("hex")}/status`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, addNop(transaction) { const _transaction = cloneDeep(transaction); const noOperation = { name: "nop", args: [randomBytes(32)], }; _transaction.operations = [..._transaction.operations, noOperation]; return _transaction; }, getTransactionRid(transaction) { try { const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRid); return gtxTool.getDigestToSign(gtx); } catch (e) { throw new GetTransactionRidException(e); } }, getTransactionsInfo(limit = 25, beforeTime, callback) { return __awaiter(this, void 0, void 0, function* () { const beforeTimeQueryParam = beforeTime ? `&before-time=${beforeTime.getTime()}` : ""; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `transactions/${this.config.blockchainRid}?limit=${limit}${beforeTimeQueryParam}`, this.config); const body = statusCode === 200 ? rspBody === null || rspBody === void 0 ? void 0 : rspBody.map(formatTransactionInfoResponse) : rspBody; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, body, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getTransactionInfo(transactionRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { isTxRidValid(transactionRid, { throwOnError: true }); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `transactions/${this.config.blockchainRid}/${toString(transactionRid)}`, this.config); const body = statusCode === 200 && rspBody ? formatTransactionInfoResponse(rspBody) : rspBody; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, body, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getTransactionCount(callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `transactions/${this.config.blockchainRid}/count`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 ? rspBody.transactionsCount : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getBlockInfo(blockIdentifier, txs = false, callback) { return __awaiter(this, void 0, void 0, function* () { isBlockIdentifierValid(blockIdentifier, { throwOnError: true }); const queryString = typeof blockIdentifier === "string" ? blockIdentifier : `height/${blockIdentifier}`; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `blocks/${this.config.blockchainRid}/${queryString}?txs=${txs}`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && rspBody !== null && rspBody ? formatBlockInfoResponse(rspBody) : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getLatestBlock(txs = false, callback) { return __awaiter(this, void 0, void 0, function* () { const shouldIncludeFullTransaction = txs ? `&txs=${txs}` : ""; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `blocks/${this.config.blockchainRid}?limit=1${shouldIncludeFullTransaction}`, this.config); const indexOfLatestBlock = 0; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && rspBody !== null && rspBody ? formatBlockInfoResponse(rspBody[indexOfLatestBlock]) : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getBlocksInfo(limit = 25, beforeTime, beforeHeight, txs, callback) { return __awaiter(this, void 0, void 0, function* () { let filteringQueryParam = ""; if (beforeTime) { filteringQueryParam = `&before-time=${beforeTime.getTime()}`; } else if (beforeHeight) { filteringQueryParam = `&before-height=${beforeHeight}`; } const shouldIncludeFullTransaction = txs ? `&txs=${txs}` : ""; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `blocks/${this.config.blockchainRid}?limit=${limit}${filteringQueryParam}${shouldIncludeFullTransaction}`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && rspBody ? rspBody.map(formatBlockInfoResponse) : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, encodeTransaction(transaction) { const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, this.config.blockchainRid); return gtxTool.serialize(gtx); }, decodeTransactionToGtx(encodedTransaction) { const gtx = gtxTool.deserialize(encodedTransaction); logger.debug(`Output from deserializing a raw transaction: ${JSON.stringify(gtx)}`); return gtx; }, getClientNodeUrlPool() { return this.config.endpointPool.map((endpoint) => endpoint.url); }, /** * Retrieves a confirmation proof for a transaction with the specified sha256 * hash. * @param txRid A buffer of 32 bytes * @param callback parameters (error, responseObjectProof) if first * parameter is not null, an error occurred. * If first parameter is null, then the second parameter is an object * like the following: * * {hash: messageHashBuffer, * blockHeader: blockHeaderBuffer, * signatures: [{pubKey: pubKeyBuffer, signature: sigBuffer}, ...], * merklePath: [{side: <0|1>, hash: <hash buffer level n-1>}, * ... * {side: <0|1>, hash: <hash buffer level 1>}]} * * If no such transaction RID exists, the callback will be called with (null, null). * * The proof object can be validated using * postchain-common.util.validateMerklePath(proof.merklePath, proof.hash, * proof.blockHeader.slice(32, 64)) * * The signatures must be validated agains some know trusted source for valid signers * at this specific block height. */ getConfirmationProof: function (txRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { isTxRidValid(txRid, { throwOnError: true }); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `tx/${this.config.blockchainRid}/${txRid.toString("hex")}/confirmationProof`, this.config); const confirmationProof = { merkleProofTree: "", txIndex: 0, }; if (statusCode === 200) { try { const decodedProof = decodeValue(toBuffer(rspBody.proof)); confirmationProof.txIndex = decodedProof.txIndex; confirmationProof.hash = decodedProof.hash; confirmationProof.blockHeader = decodedProof.blockHeader; confirmationProof.witness = decodedProof.witness; confirmationProof.merkleProofTree = decodedProof.merkleProofTree; } catch (decodeError) { if (callback) { callback(decodeError, null); } throw decodeError; } } return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 ? confirmationProof : rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, /** * Get app structure. Returns list of app modules in JSON format * @param callback - leverages error-first pattern and called when promise resolves */ getAppStructure(callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `/query/${this.config.blockchainRid}?type=rell.get_app_structure`, this.config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, rspBody, callbackPromiseBuilder(reject, resolve, callback)); }); }); }, }; }); } //# sourceMappingURL=blockchainClient.js.map