postchain-client
Version:
Client library for accessing a Postchain node through REST.
391 lines • 22.9 kB
JavaScript
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