amazon-qldb-kvs-nodejs
Version:
A helper module, simplifying basic interactions with Amazon Quantum Ledger Database for Node.js through a simple key-value store interface.
215 lines (212 loc) • 12.7 kB
JavaScript
;
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDocumentLedgerMetadataByDocIdAndTxId = exports.getDocumentLedgerMetadata = exports.lookupBlockAddressAndDocIdForKey = void 0;
const ion_js_1 = require("ion-js");
const GetDigest_1 = require("./GetDigest");
const BlockAddress_1 = require("./BlockAddress");
const Logging_1 = require("./Logging");
const logger = Logging_1.log.getLogger("qldb-helper");
const Util_1 = require("./Util");
const GetRevision_1 = require("./GetRevision");
/**
* Query the table metadata for a document with a particular key for verification.
* @param txn The {@linkcode TransactionExecutor} for lambda execute.
* @param tableName The table name to query.
* @param keyAttributeName A keyAttributeName to query.
* @param keyAttributeValue The key of the given keyAttributeName.
* @returns Promise which fulfills with a list of Ion values that contains the results of the query.
*/
function lookupBlockAddressAndDocIdForKey(txn, tableName, keyAttributeName, keyAttributeValue) {
return __awaiter(this, void 0, void 0, function* () {
const fcnName = "[GetMetadata lookupBlockAddressAndDocIdForKey]";
try {
logger.debug(`${fcnName} Querying the '${tableName}' table for key ${keyAttributeName}: ${keyAttributeValue}...`);
(0, Util_1.validateTableNameConstrains)(tableName);
(0, Util_1.validateAttributeNameConstrains)(keyAttributeName);
const query = `SELECT blockAddress, metadata.id FROM _ql_committed_${tableName} WHERE data.${keyAttributeName} = ?`;
logger.debug(`${fcnName} Constructed query: ${query}`);
const result = yield txn.execute(query, keyAttributeValue);
const resultList = result.getResultList();
return resultList;
}
catch (err) {
logger.debug(`${fcnName} ${err} `);
throw `${fcnName} ${err} `;
}
});
}
exports.lookupBlockAddressAndDocIdForKey = lookupBlockAddressAndDocIdForKey;
/**
* Retrieve full ledger metadata of the most recent revision of the document for the given Key.
* @param txn The {@linkcode TransactionExecutor} for lambda execute.
* @param ledgerName The ledger to get the digest from.
* @param tableName The table name to query.
* @param keyAttributeName A keyAttributeName to query.
* @param keyAttributeValue The key of the given keyAttributeName.
* @param qldbClient The QLDB control plane client to use.
* @returns Promise which fulfills with void.
* @throws Error: When verification fails.
*/
function getDocumentLedgerMetadata(txn, ledgerName, tableName, keyAttributeName, keyAttributeValue, qldbClient, ledgerDigest) {
return __awaiter(this, void 0, void 0, function* () {
const fcnName = "[GetMetadata getDocumentLedgerMetadata]";
try {
logger.debug(`${fcnName} Getting metadata for document with "${keyAttributeName}" = ${keyAttributeValue}, in ledger = ${ledgerName}.`);
// Getting Block Address and Document Id for the document
logger.debug(`${fcnName} Getting Block Address and Document Id for the document`);
const blockAddressAndIdList = yield lookupBlockAddressAndDocIdForKey(txn, tableName, keyAttributeName, keyAttributeValue);
logger.debug(`${fcnName} Received ${blockAddressAndIdList.length} Block Address and Document Id combination.`);
if (!blockAddressAndIdList.length) {
throw `Unable to find block address and document id associated with "${keyAttributeName}" = ${keyAttributeValue}`;
}
const blockAddressAndId = blockAddressAndIdList[blockAddressAndIdList.length - 1];
const blockAddress = (0, BlockAddress_1.blockAddressToValueHolder)(blockAddressAndId);
logger.debug(`${fcnName} Getting a proof for the document.`);
let digest = ledgerDigest;
if (!ledgerDigest) {
// Requesting ledger digest
logger.debug(`${fcnName} Requesting ledger digest`);
let ledgerDigest = yield (0, GetDigest_1.getLedgerDigest)(ledgerName, qldbClient);
// Checking if digest sequenceNo has caught up with block sequenceNo
const digestTipAddressSeqNo = ion_js_1.dom.load(ledgerDigest.DigestTipAddress.IonText).get("sequenceNo");
const blockAddressSeqNo = ion_js_1.dom.load(blockAddress.IonText).get("sequenceNo");
if (digestTipAddressSeqNo < blockAddressSeqNo) {
logger.debug(`${fcnName} The ledger digest sequenceNo is behind the block sequenceNo, so retrying after 100 ms`);
yield (0, Util_1.sleep)(100);
ledgerDigest = yield (0, GetDigest_1.getLedgerDigest)(ledgerName, qldbClient);
}
const digestBase64 = (0, ion_js_1.toBase64)(ledgerDigest.Digest);
digest = {
Digest: digestBase64,
DigestTipAddress: ledgerDigest.DigestTipAddress
};
logger.debug(`${fcnName} Got Ledger Digest: ${JSON.stringify(digest)} `);
}
// Converting digest from default buffer array to base64 format
const digestTipAddress = digest.DigestTipAddress;
logger.debug(`${fcnName} Got a ledger digest: digest tip address = ${(0, Util_1.valueHolderToString)(digestTipAddress)}, \n digest = ${digest.Digest}.`);
// Getting revision
const documentId = (0, BlockAddress_1.getMetadataId)(blockAddressAndId);
logger.debug(`${fcnName} Getting document revision with the following parameters: ${JSON.stringify({
ledgerName: ledgerName,
documentId: documentId,
blockAddress: blockAddress,
digestTipAddress: digestTipAddress
})}`);
const revisionResponse = yield (0, GetRevision_1.getRevision)(ledgerName, documentId, blockAddress, digestTipAddress, qldbClient);
const revision = ion_js_1.dom.load(revisionResponse.Revision.IonText);
const revisionHash = (0, ion_js_1.toBase64)((0, Util_1.getBlobValue)(revision, "hash"));
const proof = revisionResponse.Proof;
logger.debug(`${fcnName} Got back a proof: ${(0, Util_1.valueHolderToString)(proof)}.`);
return {
LedgerName: ledgerName,
TableName: tableName,
BlockAddress: blockAddress,
DocumentId: documentId,
RevisionHash: revisionHash,
Proof: proof,
LedgerDigest: digest
};
}
catch (err) {
throw `${fcnName} ${err} `;
}
});
}
exports.getDocumentLedgerMetadata = getDocumentLedgerMetadata;
/**
* Retrieve full ledger metadata of the most recent revision of the document for the given Key.
* @param txn The {@linkcode TransactionExecutor} for lambda execute.
* @param ledgerName The ledger to get the digest from.
* @param tableName The table name to query.
* @param keyAttributeName A keyAttributeName to query.
* @param keyAttributeValue The key of the given keyAttributeName.
* @param qldbClient The QLDB control plane client to use.
* @returns Promise which fulfills with void.
* @throws Error: When verification fails.
*/
function getDocumentLedgerMetadataByDocIdAndTxId(txn, ledgerName, tableName, documentId, transactionId, qldbClient) {
return __awaiter(this, void 0, void 0, function* () {
const fcnName = "[GetMetadata getDocumentLedgerMetadataByDocIdAndTxId]";
try {
logger.debug(`${fcnName} Getting metadata for document with documentId = ${documentId} and transactionId = ${transactionId}, in ledger = ${ledgerName} and table = ${tableName}.`);
// Getting Block Address and Document Id for the document
logger.debug(`${fcnName} Getting revision metadata for the document`);
const revisionMetadata = yield (0, GetRevision_1.getRevisionMetadataByDocIdAndTxId)(txn, tableName, documentId, transactionId);
logger.debug(`${fcnName} Received metadata ${revisionMetadata}`);
if (!revisionMetadata) {
throw `Unable to find revision metadata for documentId = ${documentId} and transactionId = ${transactionId}`;
}
//const blockAddressAndId = blockAddressAndIdList[blockAddressAndIdList.length - 1]
const blockAddress = (0, BlockAddress_1.blockAddressToValueHolder)(revisionMetadata);
logger.debug(`${fcnName} Getting a proof for the document.`);
// Requesting ledger digest
logger.debug(`${fcnName} Requesting ledger digest`);
let ledgerDigest = yield (0, GetDigest_1.getLedgerDigest)(ledgerName, qldbClient);
// Checking if digest sequenceNo has caught up with block sequenceNo
const digestTipAddressSeqNo = ion_js_1.dom.load(ledgerDigest.DigestTipAddress.IonText).get("sequenceNo");
const blockAddressSeqNo = ion_js_1.dom.load(blockAddress.IonText).get("sequenceNo");
if (digestTipAddressSeqNo < blockAddressSeqNo) {
logger.debug(`${fcnName} The ledger digest sequenceNo is behind the block sequenceNo, so retrying after 100 ms`);
yield (0, Util_1.sleep)(100);
ledgerDigest = yield (0, GetDigest_1.getLedgerDigest)(ledgerName, qldbClient);
}
const digestBase64 = (0, ion_js_1.toBase64)(ledgerDigest.Digest);
const digest = {
Digest: digestBase64,
DigestTipAddress: ledgerDigest.DigestTipAddress
};
logger.debug(`${fcnName} Got Ledger Digest: ${JSON.stringify(digest)} `);
// Converting digest from default buffer array to base64 format
const digestTipAddress = digest.DigestTipAddress;
logger.debug(`${fcnName} Got a ledger digest: digest tip address = ${(0, Util_1.valueHolderToString)(digestTipAddress)}, \n digest = ${digest.Digest}.`);
// Getting revision
logger.debug(`${fcnName} Getting document revision with the following parameters: ${JSON.stringify({
ledgerName: ledgerName,
documentId: documentId,
blockAddress: blockAddress,
digestTipAddress: digestTipAddress
})}`);
const revisionResponse = yield (0, GetRevision_1.getRevision)(ledgerName, documentId, blockAddress, digestTipAddress, qldbClient);
const revision = ion_js_1.dom.load(revisionResponse.Revision.IonText);
const revisionHash = (0, ion_js_1.toBase64)((0, Util_1.getBlobValue)(revision, "hash"));
const proof = revisionResponse.Proof;
logger.debug(`${fcnName} Got back a proof: ${(0, Util_1.valueHolderToString)(proof)}.`);
return {
LedgerName: ledgerName,
TableName: tableName,
BlockAddress: blockAddress,
DocumentId: documentId,
RevisionHash: revisionHash,
Proof: proof,
LedgerDigest: digest
};
}
catch (err) {
throw `${fcnName} ${err} `;
}
});
}
exports.getDocumentLedgerMetadataByDocIdAndTxId = getDocumentLedgerMetadataByDocIdAndTxId;