UNPKG

postchain-client

Version:

Client library for accessing a Postchain node through REST.

570 lines 27 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 * as logger from "../logger"; import { SerializedTransactionFormatException, InvalidTxRidException, TxRejectedError, UnexpectedStatusError, GetBridFromChainException, } from "./errors"; import * as gtxTool from "../gtx/gtx"; import { BlockchainUrlUndefinedException, DirectoryNodeUrlPoolException, InvalidTransactionFormatException, MissingBlockchainIdentifierError, } from "./errors"; import { ensureBuffer, toBuffer } from "../formatter"; import { createClient } from "./blockchainClient"; import { AnchoringStatus, ChainConfirmationLevel, FailoverStrategy, Method, ResponseStatus, TransactionEvent, } from "./enums"; import { createNodeManager } from "./nodeManager"; import { AnchoringTransactionSchema } from "./validation/anchoringTransaction"; import { SystemChainException } from "../ICCF/error"; import { getAnchoringClient } from "../ICCF/IccfProofTxMaterialBuilder"; import { awaitGetAnchoringTransactionForBlockRid, calculateBlockRID } from "../ICCF/utils"; import { isString } from "./validation/requests"; import { encodeTransaction } from "../utils/encodeTransaction"; import { validateMerkleHash } from "./validation/merkleHash"; import { requestWithFailoverStrategy } from "./requestWithFailoverStrategy"; import { MERKLE_HASH_VERSIONS } from "../utils/constants"; export function getClientConfigFromSettings(settings) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; return __awaiter(this, void 0, void 0, function* () { const nodeUrlPoolToUse = yield getNodeUrlsFromSettings(settings); if (nodeUrlPoolToUse === null || nodeUrlPoolToUse.length === 0) { const id = (_b = (_a = settings.blockchainRid) !== null && _a !== void 0 ? _a : settings.blockchainIid) !== null && _b !== void 0 ? _b : "Unknown"; throw new BlockchainUrlUndefinedException(id); } const endpointPool = createEndpointObjects(nodeUrlPoolToUse); const nodeManager = createNodeManager({ nodeUrls: nodeUrlPoolToUse, useStickyNode: (_c = settings.useStickyNode) !== null && _c !== void 0 ? _c : false, unavailableDuration: (_d = settings.failOverConfig) === null || _d === void 0 ? void 0 : _d.unreachableDuration, }); const blockchainRidToUse = yield (() => __awaiter(this, void 0, void 0, function* () { var _k; if (settings.blockchainRid) { return settings.blockchainRid; } if (settings.blockchainIid !== undefined) { return yield getBlockchainRidFromIid({ nodeManager, endpointPool, chainId: settings.blockchainIid, merkleHashVersion: (_k = settings.merkleHashVersion) !== null && _k !== void 0 ? _k : 0, }); } throw new MissingBlockchainIdentifierError(); }))(); const directoryChainRid = yield (() => __awaiter(this, void 0, void 0, function* () { var _l; if (settings.directoryChainRid) { return settings.directoryChainRid; } const directoryChainIid = 0; return yield getBlockchainRidFromIid({ nodeManager, endpointPool, chainId: directoryChainIid, merkleHashVersion: (_l = settings.merkleHashVersion) !== null && _l !== void 0 ? _l : 0, }); }))(); return { endpointPool, nodeManager: nodeManager, blockchainRid: blockchainRidToUse, merkleHashVersion: (_e = settings.merkleHashVersion) !== null && _e !== void 0 ? _e : 0, dappStatusPolling: setStatusPolling(settings.dappStatusPolling), clusterAnchoringStatusPolling: setStatusPolling(settings.clusterAnchoringStatusPolling), systemAnchoringStatusPolling: setStatusPolling(settings.systemAnchoringStatusPolling), failoverStrategy: ((_f = settings.failOverConfig) === null || _f === void 0 ? void 0 : _f.strategy) || defaultFailoverConfig.strategy, attemptsPerEndpoint: ((_g = settings.failOverConfig) === null || _g === void 0 ? void 0 : _g.attemptsPerEndpoint) || defaultFailoverConfig.attemptsPerEndpoint, attemptInterval: ((_h = settings.failOverConfig) === null || _h === void 0 ? void 0 : _h.attemptInterval) || defaultFailoverConfig.attemptInterval, unreachableDuration: ((_j = settings.failOverConfig) === null || _j === void 0 ? void 0 : _j.unreachableDuration) || defaultFailoverConfig.unreachableDuration, directoryChainRid: settings.directoryChainRid || directoryChainRid, }; }); } export function getMerkleHashVersionFromDapp(config) { return __awaiter(this, void 0, void 0, function* () { try { const response = yield requestWithFailoverStrategy(Method.GET, `/config/${config.blockchainRid}/features`, config); const validatedResponse = validateMerkleHash(response.rspBody); return validatedResponse.merkle_hash_version; } catch (error) { console.warn("MERKLE_HASH_WARNING: version not found or endpoint unreachable", error); return MERKLE_HASH_VERSIONS.ONE; } }); } export function validTxRid(txRID) { if (txRID.length != 32) { const error = new InvalidTxRidException(txRID); logger.error(error.toString()); return false; } return true; } export function nodeDiscovery({ nodeManager, directoryEndpointPool, failOverConfig, blockchainRid, blockchainIid, }) { return __awaiter(this, void 0, void 0, function* () { if (directoryEndpointPool.length === 0) { throw new DirectoryNodeUrlPoolException(); } if (!blockchainRid && blockchainIid === undefined) { throw new MissingBlockchainIdentifierError(); } const directoryIid = 0; const directoryBRID = yield getBlockchainRidFromIid({ nodeManager, endpointPool: directoryEndpointPool, chainId: directoryIid, failOverConfig, merkleHashVersion: 0, }); const blockchainRidToUse = yield (() => __awaiter(this, void 0, void 0, function* () { if (blockchainRid) { return blockchainRid; } if (blockchainIid !== undefined) { return yield getBlockchainRidFromIid({ nodeManager, endpointPool: directoryEndpointPool, chainId: blockchainIid, failOverConfig, merkleHashVersion: 0, }); } throw new MissingBlockchainIdentifierError(); }))(); const D1Client = yield createClient({ nodeUrlPool: getUrlsFromEndpoints(directoryEndpointPool), blockchainRid: directoryBRID, merkleHashVersion: 0, }); return yield getBlockchainApiUrls(D1Client, ensureBuffer(blockchainRidToUse)); }); } export function getBlockchainApiUrls(directoryClient, blockchainRid) { return __awaiter(this, void 0, void 0, function* () { const queryObject = { name: "cm_get_blockchain_api_urls", args: { blockchain_rid: blockchainRid }, }; return yield directoryClient.query(queryObject); }); } export function convertToRellOperation(operations) { return operations.map(operation => { var _a; return { opName: operation.name, args: (_a = operation.args) !== null && _a !== void 0 ? _a : [], }; }); } export function getSerializedGTX(gtx) { const gtxBytes = encodeTransaction(gtx); if (!Buffer.isBuffer(gtxBytes)) { throw new SerializedTransactionFormatException(); } return gtxBytes; } export function getGTXFromBufferOrTransactionOrOperation(transaction, blockchainRid) { if (Buffer.isBuffer(transaction)) { return gtxTool.deserialize(transaction); } else if ("operations" in transaction) { return { blockchainRid: ensureBuffer(blockchainRid), operations: convertToRellOperation(transaction.operations), signers: transaction.signers, signatures: [], }; } else if ("name" in transaction) { return { blockchainRid: ensureBuffer(blockchainRid), operations: convertToRellOperation([transaction]), signers: [], signatures: [], }; } else { throw new InvalidTransactionFormatException(); } } export const callbackPromiseBuilder = (reject, resolve, callback) => { return (error, result) => { var _a; if (error) { callback === null || callback === void 0 ? void 0 : callback(error, null); reject(error); } else { callback === null || callback === void 0 ? void 0 : callback(null, (_a = result) !== null && _a !== void 0 ? _a : null); resolve(result !== null && result !== void 0 ? result : null); } }; }; export const handlePostResponsePromisified = (error, statusCode, rspBody) => { return new Promise((resolve, reject) => { handlePostResponse(error, statusCode, rspBody, _error => { if (_error) { reject(_error); } else { resolve(); } }); }); }; function ensureArray(input) { if (typeof input === "string") { return [input]; } return input; } export const formatTransactionInfoResponse = (transactionInfoResponse) => { return { blockRid: toBuffer(transactionInfoResponse.blockRID), blockHeight: transactionInfoResponse.blockHeight, blockHeader: toBuffer(transactionInfoResponse.blockHeader), witness: toBuffer(transactionInfoResponse.witness), timestamp: transactionInfoResponse.timestamp, txRid: toBuffer(transactionInfoResponse.txRID), txHash: toBuffer(transactionInfoResponse.txHash), txData: toBuffer(transactionInfoResponse.txData), }; }; export const formatBlockInfoResponse = (blockInfoResponse) => { return { rid: toBuffer(blockInfoResponse.rid), prevBlockRid: toBuffer(blockInfoResponse.prevBlockRID), header: toBuffer(blockInfoResponse.header), transactions: blockInfoResponse.transactions.map(formatTransaction), height: blockInfoResponse.height, witness: toBuffer(blockInfoResponse.witness), witnesses: blockInfoResponse.witnesses.map(witness => { return toBuffer(witness); }), timestamp: blockInfoResponse.timestamp, }; }; const formatTransaction = (transaction) => { const formattedTransaction = { rid: toBuffer(transaction.rid), hash: toBuffer(transaction.hash), }; if (transaction.data !== undefined) { formattedTransaction.data = toBuffer(transaction.data); } return formattedTransaction; }; export function formatRejectedTransactionFromResponse(rejectedTransactionResponse) { return { txRid: toBuffer(rejectedTransactionResponse.txRID), rejectReason: rejectedTransactionResponse.rejectReason, rejectTimestamp: rejectedTransactionResponse.rejectTimestamp, }; } export const isKeyPair = (keypair) => { return (typeof keypair === "object" && keypair !== null && "privKey" in keypair && "pubKey" in keypair && keypair.privKey instanceof Buffer && keypair.pubKey instanceof Buffer); }; function getNodeUrlsFromSettings(settings) { var _a; return __awaiter(this, void 0, void 0, function* () { if (settings.directoryNodeUrlPool) { // If directoryNodeUrlPool is provided, use nodeDiscovery const nodeManager = createNodeManager({ nodeUrls: ensureArray(settings.directoryNodeUrlPool), unavailableDuration: (_a = settings.failOverConfig) === null || _a === void 0 ? void 0 : _a.unreachableDuration, }); const discoveredNodes = yield nodeDiscovery({ nodeManager, directoryEndpointPool: createEndpointObjects(ensureArray(settings.directoryNodeUrlPool)), failOverConfig: settings.failOverConfig, blockchainRid: settings.blockchainRid, blockchainIid: settings.blockchainIid, }); return discoveredNodes; } else if (typeof settings.nodeUrlPool === "string") { // If nodeUrlPool is a string, convert it to an array return [settings.nodeUrlPool]; } else if (Array.isArray(settings.nodeUrlPool)) { // If nodeUrlPool is already an array, use it as-is return settings.nodeUrlPool; } else { // Default to an empty array if no valid configuration is provided return []; } }); } export function getSystemClient(directoryNodeUrlPool, directoryChainRid) { return __awaiter(this, void 0, void 0, function* () { return yield createClient({ directoryNodeUrlPool, blockchainRid: directoryChainRid, merkleHashVersion: 0, }); }); } export function getSystemAnchoringChain(directoryClient) { return __awaiter(this, void 0, void 0, function* () { try { const queryObject = { name: "cm_get_system_anchoring_chain", }; const systemAnchoringChain = yield directoryClient.query(queryObject); if (!systemAnchoringChain) { throw new SystemChainException("Directory chain client must be provided."); } return systemAnchoringChain; } catch (error) { throw new SystemChainException(error.message); } }); } export const defaultFailoverConfig = { strategy: FailoverStrategy.AbortOnError, attemptsPerEndpoint: 3, attemptInterval: 500, unreachableDuration: 30000, }; export const createEndpointObjects = (endpointPoolUrls) => { const endpoints = endpointPoolUrls.map(endpointUrl => { return { url: endpointUrl, whenAvailable: 0 }; }); return endpoints; }; export const getUrlsFromEndpoints = (endpointPool) => { return endpointPool.map(endpoint => endpoint.url); }; export function awaitDappConfirmation(txRID, dappStatusPolling, getTransactionStatus) { var _a; return __awaiter(this, void 0, void 0, function* () { let lastKnownResult; for (let i = 0; i < dappStatusPolling.count; i++) { lastKnownResult = yield getTransactionStatus(txRID); if (lastKnownResult.status === ResponseStatus.Confirmed) { return lastKnownResult; } else if (lastKnownResult.status === ResponseStatus.Rejected) { throw new TxRejectedError((_a = lastKnownResult.rejectReason) !== null && _a !== void 0 ? _a : ""); } yield sleep(dappStatusPolling.interval); } // 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; }); } export function awaitClusterAnchoringChainConfirmation(txRID, clusterAnchoringStatusPolling, getClusterAnchoringTransactionConfirmation) { var _a; return __awaiter(this, void 0, void 0, function* () { let clusterAnchoringConfirmation; for (let i = 0; i < clusterAnchoringStatusPolling.count; i++) { clusterAnchoringConfirmation = yield getClusterAnchoringTransactionConfirmation(txRID); const anchoringTransactionValidaiton = AnchoringTransactionSchema.safeParse(clusterAnchoringConfirmation); if (anchoringTransactionValidaiton.success) { return { status: AnchoringStatus.ClusterAnchored, clusterAnchoredTx: anchoringTransactionValidaiton.data, }; } else if (!anchoringTransactionValidaiton.success) { throw new TxRejectedError((_a = AnchoringStatus.FailedAnchoring) !== null && _a !== void 0 ? _a : ""); } yield sleep(clusterAnchoringStatusPolling.interval); } // TS issue. This could be fixed by implementing new retry strategy // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-expect-error return clusterAnchoringConfirmation; }); } export function handleDappConfirmations(transactionRid, doStatusPolling, confirmationLevel, promiEvent, dappStatusPolling, getTransactionStatus) { return __awaiter(this, void 0, void 0, function* () { const transactionReceipt = { status: ResponseStatus.Pending, statusCode: null, transactionRid: transactionRid, }; promiEvent.emit(TransactionEvent.DappReceived, transactionReceipt); if (doStatusPolling === false || confirmationLevel === ChainConfirmationLevel.None) { return transactionReceipt; } const dappConfirmationStatus = yield awaitDappConfirmation(transactionRid, dappStatusPolling, getTransactionStatus); transactionReceipt.statusCode = dappConfirmationStatus.statusCode; transactionReceipt.status = dappConfirmationStatus.status; promiEvent.emit(TransactionEvent.DappConfirmed, transactionReceipt); if (confirmationLevel === ChainConfirmationLevel.Dapp) { return transactionReceipt; } return transactionReceipt; }); } export function handleSystemConfirmations(transactionReceipt, confirmationLevel, promiEvent, statusPollingConfig, anchoringClient, systemAnchoringChainBrid, getClusterAnchoringTransactionConfirmation, getSystemAnchoringTransactionConfirmation) { return __awaiter(this, void 0, void 0, function* () { const clusterAnchoringChainConfirmation = yield awaitClusterAnchoringChainConfirmation(transactionReceipt.transactionRid, statusPollingConfig.clusterAnchoringStatusPolling, getClusterAnchoringTransactionConfirmation); transactionReceipt.status = clusterAnchoringChainConfirmation.status; transactionReceipt.clusterAnchoredTx = clusterAnchoringChainConfirmation.clusterAnchoredTx; transactionReceipt.clusterAnchoringClientBrid = anchoringClient.config.blockchainRid; promiEvent.emit(TransactionEvent.ClusterAnchoringConfirmation, transactionReceipt); if (confirmationLevel === ChainConfirmationLevel.ClusterAnchoring) { return transactionReceipt; } const systemAnchoringConfirmation = yield getSystemAnchoringTransactionConfirmation(clusterAnchoringChainConfirmation.clusterAnchoredTx.txRid, anchoringClient, systemAnchoringChainBrid); if (!systemAnchoringConfirmation) { return transactionReceipt; } transactionReceipt.status = AnchoringStatus.SystemAnchored; transactionReceipt.systemAnchoredTx = systemAnchoringConfirmation; transactionReceipt.systemAnchoringClientBrid = systemAnchoringChainBrid; promiEvent.emit(TransactionEvent.SystemAnchoringConfirmation, transactionReceipt); if (confirmationLevel === ChainConfirmationLevel.SystemAnchoring) { return transactionReceipt; } return transactionReceipt; }); } export function getAnchoringClientAndSystemChainRid(client) { return __awaiter(this, void 0, void 0, function* () { const directoryClient = yield getSystemClient(getUrlsFromEndpoints(client.config.endpointPool), client.config.directoryChainRid); const anchoringClient = yield getAnchoringClient(directoryClient, client.config.blockchainRid); const systemAnchoringChainRidBuffer = yield getSystemAnchoringChain(directoryClient); const systemAnchoringChainBridString = systemAnchoringChainRidBuffer.toString("hex"); return { anchoringClient, systemAnchoringChainBridString }; }); } export function getSystemAnchoringTransaction(dappClientEndpointPool, anchoredTxRid, anchoringClient, systemAnchoringChainRid, systemAnchoringStatusPolling, merkleHashVersion) { return __awaiter(this, void 0, void 0, function* () { const systemAnchoringChainClient = yield getSystemClient(getUrlsFromEndpoints(dappClientEndpointPool), systemAnchoringChainRid); const clusterAnchoringProof = yield anchoringClient.getConfirmationProof(anchoredTxRid); const clusterBlockRid = calculateBlockRID(clusterAnchoringProof, merkleHashVersion); const systemAnchoringTransactionConfirmation = yield awaitGetAnchoringTransactionForBlockRid(systemAnchoringChainClient, toBuffer(anchoringClient.config.blockchainRid), clusterBlockRid, systemAnchoringStatusPolling); const systemAnchoringTransactionValidation = AnchoringTransactionSchema.safeParse(systemAnchoringTransactionConfirmation); if (!systemAnchoringTransactionValidation.success) { return null; } return systemAnchoringTransactionValidation.data; }); } export function setStatusPolling(statusPolling) { var _a, _b; return { interval: (_a = statusPolling === null || statusPolling === void 0 ? void 0 : statusPolling.interval) !== null && _a !== void 0 ? _a : 500, count: (_b = statusPolling === null || statusPolling === void 0 ? void 0 : statusPolling.count) !== null && _b !== void 0 ? _b : 20, }; } export function linkPromiEvents(event1, event2) { const transactionEvents = Object.values(TransactionEvent).filter((event) => typeof event === "string"); /* eslint-disable @typescript-eslint/no-explicit-any */ const forwardEvent = (eventName) => (receipt) => event2.emit(eventName, receipt); for (const eventName of transactionEvents) { event1.on(eventName, forwardEvent(eventName)); } } /** * @param error response error * @param statusCode response status code * @param responseObject the responsebody from the server * @param callback the callback function to propagate the error and response back to the caller */ export function handleGetResponse(error, statusCode, responseObject, callback) { try { const responseObjectPrintable = convertToPrintable(responseObject); logger.debug(`error: ${error}, status code: ${statusCode}, response body: ${responseObjectPrintable}`); if (error) { return callback(error, null); } if (statusCode !== 200) { return callback(new UnexpectedStatusError(statusCode !== null && statusCode !== void 0 ? statusCode : 400, responseObjectPrintable), null); } return callback(null, responseObject); } catch (error) { callback(error, null); logger.error(`client.handleGetResponse(): Failed to call the callback function. ${error}`); } } /** * @param error response error * @param statusCode response status code * @param responseObject the responsebody from the server * @param callback the callback function to propagate the error and response back to the caller */ export function handlePostResponse(error, statusCode, responseObject, callback) { const responseObjectPrintable = convertToPrintable(responseObject); logger.debug(`error: ${error}, status code: ${statusCode}, response body: ${responseObjectPrintable}`); try { if (error) { logger.error(`In client post(). ${error}`); callback(error, null); } else if (statusCode != 200) { let errorMessage = `Unexpected status code from server. Code: ${statusCode}.`; if (responseObjectPrintable) { errorMessage += ` Message: ${responseObjectPrintable}.`; } logger.error(errorMessage); callback(new UnexpectedStatusError(statusCode !== null && statusCode !== void 0 ? statusCode : 400, responseObjectPrintable), responseObject); } else { logger.info(`Calling responseCallback with responseObject: ${responseObjectPrintable}`); callback(null, responseObject); } } catch (error) { logger.error(`client.handlePostResponse(): Failed to call callback function ${error}`); } } export function getBlockchainRidFromIid({ endpointPool, chainId, failOverConfig = {}, nodeManager, }) { return __awaiter(this, void 0, void 0, function* () { const mergedFailOverConfig = Object.assign(Object.assign({}, defaultFailoverConfig), failOverConfig); const config = { endpointPool, nodeManager, dappStatusPolling: setStatusPolling(), clusterAnchoringStatusPolling: setStatusPolling(), systemAnchoringStatusPolling: setStatusPolling(), failoverStrategy: mergedFailOverConfig.strategy, attemptsPerEndpoint: mergedFailOverConfig.attemptsPerEndpoint, attemptInterval: mergedFailOverConfig.attemptInterval, unreachableDuration: mergedFailOverConfig.unreachableDuration, merkleHashVersion: 0, }; const { error, statusCode, rspBody } = yield requestWithFailoverStrategy(Method.GET, `/brid/iid_${chainId}`, config); const blockchainRid = isString(rspBody); if (!blockchainRid) { throw error; } if (error) { throw new GetBridFromChainException(chainId, error.message); } else if (statusCode !== 200) { throw new GetBridFromChainException(chainId, blockchainRid); } return blockchainRid; }); } export const sleep = (ms) => new Promise(r => setTimeout(r, ms)); export function convertToPrintable(responseObject) { if (typeof responseObject === "bigint") { return `${responseObject}n`; } else if (typeof responseObject === "object") { return JSON.stringify(responseObject, (key, value) => typeof value === "bigint" ? `${value}n` : value); } else { return String(responseObject); } } //# sourceMappingURL=utils.js.map