UNPKG

postchain-client

Version:

Client library for accessing a Postchain node through REST.

536 lines 34.5 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 { getBlockAnchoringTransaction } from "../ICCF/IccfProofTxMaterialBuilder"; import * as logger from "../logger"; import { toBuffer, toString, toQueryObjectGTV, ensureString, ensureBuffer } from "../formatter"; import { decodeValue, encodeValue } from "../gtx/serialization"; import * as gtxTool from "../gtx/gtx"; import { callbackPromiseBuilder, formatBlockInfoResponse, formatTransactionInfoResponse, getClientConfigFromSettings, getGTXFromBufferOrTransactionOrOperation, getSerializedGTX, handlePostResponsePromisified, handleSystemConfirmations, isKeyPair, getAnchoringClientAndSystemChainRid, getSystemAnchoringTransaction, linkPromiEvents, handleGetResponse, handlePostResponse, handleDappConfirmations, getMerkleHashVersionFromDapp, formatRejectedTransactionFromResponse, } from "./utils"; import { Web3PromiEvent } from "../promiEvent/promiEvents"; import { NumberOfSignersAndSignaturesException } from "../gtx/errors"; import { ResponseStatus, ChainConfirmationLevel, AnchoringStatus, TransactionEvent, Method, } from "./enums"; import { isBlockIdentifierValid } from "./validation/blockIdentifier"; import { isNetworkSettingValid } from "./validation/networkSettings"; import { isSignMethodValid } from "./validation/signMethod"; import { isTxRidValid } from "./validation/txRid"; import { AnchoringTransactionSchema } from "./validation/anchoringTransaction"; import { isBlockInfoResponse, isBlockInfoResponseArray, isRawGtv, isRejectedTransactionResponseArray, isTransaction, isTransactionConfirmationProof, isTransactionInfoResponse, isTransactionInfoResponseArray, isTransactionsCount, } from "./validation/requests"; import { awaitGetAnchoringTransactionForBlockRid } from "../ICCF/utils"; import { requestWithFailoverStrategy } from "./requestWithFailoverStrategy"; import { validateTransactionStatusReponse } from "./validation/transactionStatusReponse"; export function createClient(settings) { return __awaiter(this, void 0, void 0, function* () { isNetworkSettingValid(settings, { throwOnError: true }); const config = yield getClientConfigFromSettings(settings); config.merkleHashVersion = yield getMerkleHashVersion(settings); return { config, 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/${config.blockchainRid}`, config, encodeValue(toQueryObjectGTV(_name, _args))); return new Promise((resolve, reject) => { handlePostResponse(error, statusCode, isRawGtv(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, config.blockchainRid); try { const signedTx = yield (isKeyPair(signMethod) ? gtxTool.sign(gtx, signMethod.privKey, config.merkleHashVersion, signMethod.pubKey) : gtxTool.sign(gtx, signMethod, config.merkleHashVersion)); const gtxBytes = getSerializedGTX(signedTx); if (typeof callback === "function") { callback(null, gtxBytes); } return gtxBytes; } catch (error) { if (typeof callback === "function") { callback(error, null); } // Todo the error should be replaced with the custom error MissingSignerException throw new Error(error); } }); }, sendTransaction(transaction, doStatusPolling = true, callback, confirmationLevel = ChainConfirmationLevel.Dapp) { const promiEvent = new Web3PromiEvent((resolve, reject) => __awaiter(this, void 0, void 0, function* () { var _a; try { const gtx = getGTXFromBufferOrTransactionOrOperation(transaction, 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 { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.POST, `tx/${config.blockchainRid}`, config, gtxBytes); const transactionRid = gtxTool.getDigestToSign(gtx, config.merkleHashVersion); try { yield handlePostResponsePromisified(error, statusCode, isRawGtv(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 client = this; const statusPollingConfig = { dappStatusPolling: client.config.dappStatusPolling, clusterAnchoringStatusPolling: client.config.clusterAnchoringStatusPolling, systemAnchoringStatusPolling: client.config.systemAnchoringStatusPolling, }; let transactionReceipt = yield handleDappConfirmations(transactionRid, doStatusPolling, confirmationLevel, promiEvent, statusPollingConfig.dappStatusPolling, () => client.getTransactionStatus(transactionRid, callback)); if (confirmationLevel === ChainConfirmationLevel.None || confirmationLevel === ChainConfirmationLevel.Dapp) { resolve(transactionReceipt); return; } const { anchoringClient, systemAnchoringChainBridString } = yield getAnchoringClientAndSystemChainRid(client); transactionReceipt = yield handleSystemConfirmations(transactionReceipt, confirmationLevel, promiEvent, statusPollingConfig, anchoringClient, systemAnchoringChainBridString, () => client.getClusterAnchoringTransactionConfirmation(transactionRid, anchoringClient, callback), anchoredTxRid => client.getSystemAnchoringTransactionConfirmation(anchoredTxRid, anchoringClient, systemAnchoringChainBridString, callback)); resolve(transactionReceipt); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); reject(error); } })); return promiEvent; }, signAndSendUniqueTransaction(transactionOrOperation, signMethod, doStatusPolling = true, callback, confirmationLevel = ChainConfirmationLevel.Dapp) { isSignMethodValid(signMethod, { throwOnError: true }); const promiEvent = new Web3PromiEvent((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const client = this; 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 : client.addNop(transaction); try { const signedTx = yield client.signTransaction(transactionWithNop, signMethod); promiEvent.emit(TransactionEvent.Signed, signedTx); const sendTransactionPromiEvent = client.sendTransaction(signedTx, doStatusPolling, callback, confirmationLevel); linkPromiEvents(sendTransactionPromiEvent, promiEvent); resolve(yield 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/${config.blockchainRid}/${ensureString(transactionRid)}`, config); const transaction = isTransaction(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && transaction ? toBuffer(transaction.tx) : isRawGtv(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/${config.blockchainRid}/${transactionRid.toString("hex")}/status`, config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(rspBody), callbackPromiseBuilder(reject, result => resolve(validateTransactionStatusReponse({ statusCode, result, })), callback)); }); }); }, addNop(transaction) { const _transaction = cloneDeep(transaction); const noOperation = { name: "nop", args: [randomBytes(32)], }; _transaction.operations = [..._transaction.operations, noOperation]; return _transaction; }, getTransactionsInfo(limit = 25, beforeTime, callback, afterTime, signerPubKey) { return __awaiter(this, void 0, void 0, function* () { const beforeTimeQueryParam = beforeTime ? `&before-time=${beforeTime.getTime()}` : ""; const afterTimeQueryParam = afterTime ? `&after-time=${afterTime.getTime()}` : ""; const signerQueryParam = signerPubKey ? `&signer=${signerPubKey}` : ""; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `transactions/${config.blockchainRid}?limit=${limit}${beforeTimeQueryParam}${afterTimeQueryParam}${signerQueryParam}`, config); const transactionsInfoResponseArray = isTransactionInfoResponseArray(rspBody); const body = statusCode === 200 && transactionsInfoResponseArray ? transactionsInfoResponseArray.map(formatTransactionInfoResponse) : rspBody; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(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/${config.blockchainRid}/${ensureString(transactionRid)}`, config); const transactionInfoResponse = isTransactionInfoResponse(rspBody); const body = statusCode === 200 && transactionInfoResponse ? formatTransactionInfoResponse(transactionInfoResponse) : rspBody; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(body), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getAnchoringStatusForBlockRid(blockRid, anchoringClient, systemAnchoringChainRid) { return __awaiter(this, void 0, void 0, function* () { const client = this; const result = { status: AnchoringStatus.NotAnchored }; const clusterAnchoredTx = yield awaitGetAnchoringTransactionForBlockRid(anchoringClient, ensureBuffer(client.config.blockchainRid), ensureBuffer(blockRid), config.clusterAnchoringStatusPolling); if (!clusterAnchoredTx) { return result; } const anchoringTransactionValidation = AnchoringTransactionSchema.safeParse(clusterAnchoredTx); if (!anchoringTransactionValidation.success) { return result; } result.status = AnchoringStatus.ClusterAnchored; result.clusterAnchoredTx = anchoringTransactionValidation.data; const systemAnchoredTransaction = yield getSystemAnchoringTransaction(config.endpointPool, anchoringTransactionValidation.data.txRid, anchoringClient, systemAnchoringChainRid, config.systemAnchoringStatusPolling, config.merkleHashVersion); if (!systemAnchoredTransaction) { return result; } result.status = AnchoringStatus.SystemAnchored; result.systemAnchoredTx = systemAnchoredTransaction; return result; }); }, getTransactionConfirmationLevel(transactionRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { const client = this; const dappConfirmation = yield client.getTransactionStatus(transactionRid); const response = { status: dappConfirmation.status, statusCode: dappConfirmation.statusCode, transactionRid: ensureBuffer(transactionRid), }; if (dappConfirmation.status === ResponseStatus.Waiting) { return response; } let clusterAnchoringResult = null; if (dappConfirmation.status === ResponseStatus.Confirmed) { const { anchoringClient, systemAnchoringChainBridString } = yield getAnchoringClientAndSystemChainRid(client); clusterAnchoringResult = yield client.getClusterAnchoringTransactionConfirmation(transactionRid, anchoringClient); if (clusterAnchoringResult === AnchoringStatus.NotAnchored || !clusterAnchoringResult) { return response; } if (clusterAnchoringResult === AnchoringStatus.FailedAnchoring) { response.status = AnchoringStatus.FailedAnchoring; response.statusCode = 400; return response; } const anchoringTransactionValidation = AnchoringTransactionSchema.safeParse(clusterAnchoringResult); if (!anchoringTransactionValidation.success) { return response; } if (anchoringTransactionValidation.success) { response.status = AnchoringStatus.ClusterAnchored; response.statusCode = 200; response.clusterAnchoredTx = anchoringTransactionValidation.data; response.clusterAnchoringClientBrid = anchoringClient.config.blockchainRid; const systemAnchoringResult = yield client.getSystemAnchoringTransactionConfirmation(anchoringTransactionValidation.data.txRid, anchoringClient, systemAnchoringChainBridString); if (!systemAnchoringResult) { return response; } response.status = AnchoringStatus.SystemAnchored; response.systemAnchoredTx = systemAnchoringResult; response.systemAnchoringClientBrid = systemAnchoringChainBridString; return response; } } return response; } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } }); }, getTransactionCount(callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `transactions/${config.blockchainRid}/count`, config, undefined, true); const transactionsCount = isTransactionsCount(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 ? transactionsCount === null || transactionsCount === void 0 ? void 0 : transactionsCount.transactionsCount : isRawGtv(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/${config.blockchainRid}/${queryString}?txs=${txs}`, config); const blockInfoResponse = isBlockInfoResponse(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && blockInfoResponse !== null && blockInfoResponse ? formatBlockInfoResponse(blockInfoResponse) : isRawGtv(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/${config.blockchainRid}?limit=1${shouldIncludeFullTransaction}`, config, undefined, true); const indexOfLatestBlock = 0; const blockInfoResponseArray = isBlockInfoResponseArray(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && blockInfoResponseArray !== null && blockInfoResponseArray ? formatBlockInfoResponse(blockInfoResponseArray[indexOfLatestBlock]) : isRawGtv(rspBody), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getBlocks({ limit, beforeTime, afterTime, beforeHeight, afterHeight, txs, callback, } = {}) { return __awaiter(this, void 0, void 0, function* () { const searchValues = { limit, txs, "before-time": beforeTime, "after-time": afterTime, "before-height": beforeHeight, "after-height": afterHeight, }; const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(searchValues)) { if (value !== undefined) { searchParams.append(key, value.toString()); } } const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `blocks/${config.blockchainRid}?${searchParams}`, config); const blockInfoResponseArray = isBlockInfoResponseArray(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, statusCode === 200 && blockInfoResponseArray ? blockInfoResponseArray.map(formatBlockInfoResponse) : isRawGtv(rspBody), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getClientNodeUrlPool() { return 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(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/${config.blockchainRid}/${ensureString(txRid)}/confirmationProof`, config); const confirmationProof = { merkleProofTree: "", txIndex: 0, }; if (statusCode === 200) { try { const transactionConfirmationProof = isTransactionConfirmationProof(rspBody); if (!transactionConfirmationProof) { throw error; } const decodedProof = decodeValue(toBuffer(transactionConfirmationProof.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 : isRawGtv(rspBody), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getClusterAnchoringTransactionConfirmation(transactionRid, anchoringClient, 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 client = this; try { let confirmationProof; try { confirmationProof = yield client.getConfirmationProof(transactionRid); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); return AnchoringStatus.NotAnchored; } const anchoringTransaction = yield getBlockAnchoringTransaction(client, anchoringClient, transactionRid, confirmationProof); if (!anchoringTransaction) return AnchoringStatus.NotAnchored; return anchoringTransaction; } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); return AnchoringStatus.FailedAnchoring; } }); }, getSystemAnchoringTransactionConfirmation(anchoredTxRid, anchoringClient, systemAnchoringChainRid, callback) { return __awaiter(this, void 0, void 0, function* () { try { isTxRidValid(anchoredTxRid, { throwOnError: true }); } catch (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); throw error; } const systemAnchoringTransactionTransaction = yield getSystemAnchoringTransaction(config.endpointPool, anchoredTxRid, anchoringClient, systemAnchoringChainRid, config.systemAnchoringStatusPolling, config.merkleHashVersion); return systemAnchoringTransactionTransaction; }); }, getWaitingTransactions(blockchainRid, callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `/tx/${ensureString(blockchainRid)}/waiting`, config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(rspBody), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, getWaitingTransaction(blockchainRid, txRid, callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody, transactionTimestamp } = yield requestWithFailoverStrategy(Method.GET, `/tx/${ensureString(blockchainRid)}/waiting/${ensureString(txRid)}`, config); const transaction = isTransaction(rspBody); const result = transaction ? toBuffer(transaction.tx) : isRawGtv(rspBody); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, result, callbackPromiseBuilder(reject, txData => resolve({ txData: isRawGtv(txData), transactionTimestamp: Number(transactionTimestamp), }), callback)); }); }); }, getRejectedTransactions(blockchainRid, callback) { return __awaiter(this, void 0, void 0, function* () { const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `/tx/${ensureString(blockchainRid)}/rejected`, config); const rejectedTransactionsArray = isRejectedTransactionResponseArray(rspBody); const validResponseBody = statusCode === 200 && rejectedTransactionsArray ? rejectedTransactionsArray.map(rejectedTransaction => formatRejectedTransactionFromResponse(rejectedTransaction)) : rspBody; return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(validResponseBody), 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/${config.blockchainRid}?type=rell.get_app_structure`, config); return new Promise((resolve, reject) => { handleGetResponse(error, statusCode, isRawGtv(rspBody), callbackPromiseBuilder(reject, resolve, callback)); }); }); }, }; function getMerkleHashVersion(settings) { return __awaiter(this, void 0, void 0, function* () { let merkleHashVersion; if (settings.merkleHashVersion !== undefined) { merkleHashVersion = settings.merkleHashVersion; } else { merkleHashVersion = yield getMerkleHashVersionFromDapp(config); } return merkleHashVersion; }); } }); } //# sourceMappingURL=blockchainClient.js.map