From 4c6a0ba77c031d15bc72ec31f3f5ee94d6b1b52a Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 25 Jan 2023 12:28:29 +0000 Subject: [PATCH] New apis added for fetch data by blocknumber blockrange transactionid Signed-off-by: Anusha Modified function names Signed-off-by: Anusha code formatted as per review comments Signed-off-by: Anusha --- app/persistence/fabric/CRUDService.ts | 24 +++ app/platform/fabric/Proxy.ts | 192 +++++++++++++++++++ app/platform/fabric/gateway/FabricGateway.ts | 22 +++ app/platform/fabric/sync/SyncService.ts | 4 +- app/rest/platformroutes.ts | 67 +++++++ 5 files changed, 307 insertions(+), 2 deletions(-) diff --git a/app/persistence/fabric/CRUDService.ts b/app/persistence/fabric/CRUDService.ts index 3ab5ce34e..fa78e7597 100644 --- a/app/persistence/fabric/CRUDService.ts +++ b/app/persistence/fabric/CRUDService.ts @@ -475,6 +475,30 @@ export class CRUDService { } } // Orderer BE-303 + + /** + * + * Returns the block by block number. + * + * @param {*} channel_genesis_hash + * @param {*} blockNo + * @returns + * @memberof CRUDService + */ + async getBlockByBlocknum(network_name:any, channel_genesis_hash:any, blockNo:any) { + const sqlBlockTxList = `select a.* from ( + select (select c.name from channel c where c.channel_genesis_hash =$1 and c.network_name = $2) + as channelname, blocks.blocknum,blocks.txcount ,blocks.datahash ,blocks.blockhash ,blocks.prehash,blocks.createdt, blocks.blksize, ( + SELECT array_agg(txhash) as txhash FROM transactions where blockid = $3 and + channel_genesis_hash = $1 and network_name = $2) from blocks where + blocks.channel_genesis_hash =$1 and blocks.network_name = $2 and blocknum = $3) a where a.txhash IS NOT NULL`; + + const row: any = await this.sql.getRowsBySQlCase( + sqlBlockTxList, + [channel_genesis_hash, network_name, blockNo]); + return row; + } + } /** diff --git a/app/platform/fabric/Proxy.ts b/app/platform/fabric/Proxy.ts index 65505a931..c9d5c5d97 100644 --- a/app/platform/fabric/Proxy.ts +++ b/app/platform/fabric/Proxy.ts @@ -8,6 +8,10 @@ import { NetworkService } from './service/NetworkService'; import { ExplorerError } from '../../common/ExplorerError'; import { explorerError } from '../../common/ExplorerMessage'; import * as FabricConst from './utils/FabricConst'; +import { SyncPlatform } from './sync/SyncPlatform'; +import { convertValidationCode, jsonObjSize, SyncServices } from './sync/SyncService'; +import * as sha from 'js-sha256'; +import * as FabricUtils from './utils/FabricUtils'; const fabric_const = FabricConst.fabric.const; @@ -391,4 +395,192 @@ export class Proxy { ); } } + + /** + * + * + * @param {*} channel_genesis_hash + * @param {*} blockNo + * @returns + * @memberof Proxy + */ + async fetchDataByBlockNo(network_id: string, channel_genesis_hash: string, blockNo: number) { + return await this.dataByBlockNo(network_id, channel_genesis_hash, blockNo); + } + + /** + * + * + * @param {*} channel_genesis_hash + * @param {*} txnId + * @returns + * @memberof Proxy + */ + async fetchDataByTxnId(network_id: string, channel_genesis_hash: string, txnId: string) { + const results = await this.persistence.getCrudService().getTransactionByID(network_id, channel_genesis_hash, txnId); + if (results == null) { + return await this.queryTxFromLedger(network_id, channel_genesis_hash, txnId); + } + return results; + } + + async queryTxFromLedger(network_id: string, channel_genesis_hash: string, txnId: string) { + let syncPlatform = new SyncPlatform(this.persistence, null) + let sync = new SyncServices(syncPlatform, this.persistence); + const client = this.platform.getClient(network_id); + const channel_name = client.getChannelNameByHash(channel_genesis_hash); + try { + const txn = await client.fabricGateway.queryTransaction(channel_name, txnId); + logger.info("Transaction details from query Transaction ", txn); + if (txn) { + //Formatting of transaction details + const txObj = txn.transactionEnvelope; + const txStr = JSON.stringify(txObj); + let txid = txObj.payload.header.channel_header.tx_id; + let validation_code = ''; + let payload_proposal_hash = ''; + let chaincode = ''; + let rwset; + let readSet; + let writeSet; + let chaincodeID; + let mspId = []; + + sync.convertFormatOfValue( + 'value', + client.fabricGateway.fabricConfig.getRWSetEncoding(), + txObj + ); + if (txid && txid !== '') { + const val_code = txn.validationCode; + validation_code = convertValidationCode(val_code); + } + if (txObj.payload.data.actions !== undefined) { + chaincode = + txObj.payload.data.actions[0].payload.action.proposal_response_payload + .extension.chaincode_id.name; + chaincodeID = new Uint8Array( + txObj.payload.data.actions[0].payload.action.proposal_response_payload.extension + ); + mspId = txObj.payload.data.actions[0].payload.action.endorsements.map( + endorsement => endorsement.endorser.mspid + ); + rwset = + txObj.payload.data.actions[0].payload.action.proposal_response_payload + .extension.results.ns_rwset; + + readSet = rwset.map(rw => ({ + chaincode: rw.namespace, + set: rw.rwset.reads + })); + + writeSet = rwset.map(rw => ({ + chaincode: rw.namespace, + set: rw.rwset.writes + })); + + payload_proposal_hash = txObj.payload.data.actions[0].payload.action.proposal_response_payload.proposal_hash.toString( + 'hex' + ); + } + if (txObj.payload.header.channel_header.typeString === 'CONFIG') { + txid = sha.sha256(txStr); + readSet = + txObj.payload.data.last_update.payload?.data.config_update.read_set; + writeSet = + txObj.payload.data.last_update.payload?.data.config_update.write_set; + } + + const chaincode_id = String.fromCharCode.apply(null, chaincodeID); + const transaction = { + channel_name, + txhash: txid, + createdt: txObj.payload.header.channel_header.timestamp, + chaincodename: chaincode, + chaincode_id, + creator_msp_id: txObj.payload.header.signature_header.creator.mspid, + endorser_msp_id: mspId, + type: txObj.payload.header.channel_header.typeString, + readSet, + writeSet, + validation_code, + payload_proposal_hash, + }; + return transaction; + } + return txn; + } + catch (e) { + logger.debug('No transaction found with this txn id >> ', e); + } + } + + /** + * + * + * @param {*} channel_genesis_hash + * @param {*} startBlockNo + * @param {*} endBlockNo + * @returns + * @memberof Proxy + */ + async fetchDataByBlockRange(network_id: string, channel_genesis_hash: string, startBlockNo: number, endBlockNo: number) { + let blockValue, blockArray = []; + for (let index = startBlockNo; index <= endBlockNo; index++) { + blockValue = await this.dataByBlockNo(network_id, channel_genesis_hash, index); + if (blockValue != "response_payloads is null") { + blockArray.push(blockValue); + } + else + break; + } + if (blockArray.length > 0) { + return blockArray; + } + return blockValue; + } + + //Re-usable component to fetch data using block no and block range + async dataByBlockNo(network_id: string, channel_genesis_hash: string, blockNo: number) { + const client = this.platform.getClient(network_id); + const channel_name = client.getChannelNameByHash(channel_genesis_hash); + //fetch data from postgress + const results = await this.persistence.getCrudService().getBlockByBlocknum(network_id, channel_genesis_hash, blockNo); + if (results == null) { + const block = await this.getBlockByNumber(network_id, channel_genesis_hash, blockNo); + if (block != "response_payloads is null") { + logger.info("block details from gateway", block); + const first_tx = block.data.data[0]; + const header = first_tx.payload.header; + const createdt = await FabricUtils.getBlockTimeStamp( + header.channel_header.timestamp + ); + const blockhash = await FabricUtils.generateBlockHash(block.header); + //For transaction id + const txLen = block.data.data.length; + let txArray = []; + for (let txIndex = 0; txIndex < txLen; txIndex++) { + const txObj = block.data.data[txIndex]; + let txid = txObj.payload.header.channel_header.tx_id; + txArray.push(txid); + } + const blockData = { + channelname: channel_name, + blocknum: block.header.number.toString(), + datahash: block.header.data_hash.toString('hex'), + prehash: block.header.previous_hash.toString('hex'), + txcount: block.data.data.length, + createdt, + prev_blockhash: '', + blockhash, + channel_genesis_hash, + blksize: jsonObjSize(block), + txhash: txArray + }; + return blockData; + } + return block; + } + return results; + } } diff --git a/app/platform/fabric/gateway/FabricGateway.ts b/app/platform/fabric/gateway/FabricGateway.ts index 49251c0b7..b808f3d64 100644 --- a/app/platform/fabric/gateway/FabricGateway.ts +++ b/app/platform/fabric/gateway/FabricGateway.ts @@ -488,4 +488,26 @@ export class FabricGateway { return null; } + + async queryTransaction(channelName:string, txnId:string) { + try { + const network = await this.gateway.getNetwork(this.defaultChannelName); + // Get the contract from the network. + const contract = network.getContract('qscc'); + const resultByte = await contract.evaluateTransaction( + 'GetTransactionByID', + channelName, + txnId + ); + const resultJson = BlockDecoder.decodeTransaction(resultByte); + logger.debug('queryTransaction', resultJson); + return resultJson; + } catch (error) { + logger.error( + `Failed to get transaction ${txnId} from channel ${channelName} : `, + error + ); + return null; + } + } } diff --git a/app/platform/fabric/sync/SyncService.ts b/app/platform/fabric/sync/SyncService.ts index 1edc9751a..b4e7dac96 100644 --- a/app/platform/fabric/sync/SyncService.ts +++ b/app/platform/fabric/sync/SyncService.ts @@ -758,7 +758,7 @@ export class SyncServices { } } // Transaction validation code -function convertValidationCode(code) { +export function convertValidationCode(code) { if (typeof code === 'string') { return code; } @@ -766,7 +766,7 @@ function convertValidationCode(code) { } // Calculate data size of json object -function jsonObjSize(json) { +export function jsonObjSize(json) { let bytes = 0; function sizeOf(obj) { diff --git a/app/rest/platformroutes.ts b/app/rest/platformroutes.ts index 7bb540f2f..3d1c35c34 100644 --- a/app/rest/platformroutes.ts +++ b/app/rest/platformroutes.ts @@ -180,4 +180,71 @@ export async function platformroutes( }); }); }); + + /** + * * + * Block by block number + * GET /fetchDataByBlockNo + * curl -i 'http://:/fetchDataByBlockNo//' + */ + router.get('/fetchDataByBlockNo/:channel_genesis_hash/:blockNo', (req, res) => { + const blockNo = parseInt(req.params.blockNo); + const channel_genesis_hash = req.params.channel_genesis_hash; + proxy.fetchDataByBlockNo(req.network, channel_genesis_hash, blockNo).then((data: any) => { + if (data != "response_payloads is null") { + res.send({ status: 200, data: data }); + } + else{ + res.send({ status: 404, data: "Block not found" }); + } + }); + }); + + /** + * * + * Blocks by block range + * GET /fetchDataByBlockRange + * curl -i 'http://:/fetchDataByBlockRange///' + */ + router.get('/fetchDataByBlockRange/:channel_genesis_hash/:startBlockNo/:endBlockNo', (req, res) => { + const startBlockNo = parseInt(req.params.startBlockNo); + const endBlockNo = parseInt(req.params.endBlockNo); + const channel_genesis_hash = req.params.channel_genesis_hash; + if (startBlockNo < endBlockNo) { + proxy.fetchDataByBlockRange(req.network, channel_genesis_hash, startBlockNo, endBlockNo).then((data: any) => { + if (data != "response_payloads is null") { + res.send({ status: 200, data: data }); + } + else{ + res.send({ status: 404, data: "Block(s) not found" }); + } + }); + } + else { + return requtil.invalidRequest(req, res); + } + + }); + + + /** + * * + * Transaction by txn id + * GET /fetchDataByTxnId + * curl -i 'http://:/fetchDataByTxnId//' + */ + router.get('/fetchDataByTxnId/:channel_genesis_hash/:txnId', (req, res) => { + const txnId = req.params.txnId; + const channel_genesis_hash = req.params.channel_genesis_hash; + proxy.fetchDataByTxnId(req.network, channel_genesis_hash, txnId).then((data: any) => { + if (data != null) { + res.send({ status: 200, data: data }); + } + else{ + res.send({ status: 404, data: "Transaction not found" }); + } + }); + }); + + } // End platformroutes()