diff --git a/.cspell.json b/.cspell.json index 0da7555978..c73215f442 100644 --- a/.cspell.json +++ b/.cspell.json @@ -33,6 +33,8 @@ "cccs", "ccep", "ccid", + "cctx", + "cctxviz", "celo", "cids", "clsx", @@ -148,6 +150,7 @@ "qscc", "recoverupdateackmessage", "rogpeppe", + "rabbitmq", "RUSTC", "Rwset", "satp", @@ -178,6 +181,7 @@ "unixfs", "Unmarshal", "uuidv", + "Visualizable", "vscc", "vuln", "wasm", diff --git a/jest.config.js b/jest.config.js index cde611ff46..a2ea8d365f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -132,5 +132,13 @@ module.exports = { `./examples/cactus-example-carbon-accounting-backend/src/test/typescript/integration/admin-enroll-v1-endpoint.test.ts`, `./examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-backend-api-calls.test.ts`, `./examples/cactus-example-supply-chain-backend/src/test/typescript/integration/supply-chain-cli-via-npm-script.test.ts`, + `./examples/cactus-check-connection-ethereum-validator/src/test/typescript/integration/check-connection-to-ledger.test.ts`, + `./examples/cactus-check-connection-ethereum-validator/src/test/typescript/integration/check-config-files.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-rabbitmq.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-baseline-events.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-invalid.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-persist-cross-chain-log.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-cctxviz-usecase-fabric-besu-6-events.test.ts`, + `./packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-rabbitmq.test.ts`, ], }; diff --git a/package.json b/package.json index 3b1c0d33a1..ffc0380db5 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,22 @@ "build:dev:frontend": "lerna run build:dev:frontend --scope='@hyperledger/cactus-example-*-frontend' --scope='@hyperledger/cacti-ledger-browser'", "build:dev:common": "lerna exec --stream --scope '*/*common' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:backend:postbuild": "lerna run build:dev:backend:postbuild", + "test:cmd-api-server": "tap --ts --timeout=600 \"packages/cactus-*cmd-api-server/src/test/typescript/{unit,integration}/\"", + "test:plugin-ledger-connector-besu": "tap --ts --jobs=1 --timeout=60 \"packages/cactus-*-besu/src/test/typescript/{unit,integration}/\"", + "test:plugin-htlc-besu-erc20": "tap --jobs=1 --timeout=600 \"packages/*htlc-eth-besu-erc20/src/test/typescript/{unit,integration}/\"", + "test:plugin": "tap --jobs=1 --timeout=600 \"packages/*test-plugin-htlc-eth-besu/src/test/typescript/{unit,integration}/\"", + "test:plugin-ledger-connector-quorum": "tap --ts --jobs=1 --timeout=60 \"packages/cactus-*-quorum/src/test/typescript/{unit,integration}/\"", + "test:cctxviz": "tap --jobs=1 --timeout=600 \"packages/cactus-plugin-cc-tx-visualization/src/test/typescript/{unit,integration}/\"", + + "test:plugin-ledger-connector-iroha": "tap --ts --jobs=1 --timeout=600 \"packages/cactus-*-iroha/src/test/typescript/{unit,integration}/\"", + "test:plugin-htlc-besu": "tap --jobs=1 --timeout=600 \"packages/*htlc-eth-besu/src/test/typescript/{integration}/\"", + "build:dev:plugin-consortium-manual": "lerna exec --stream --scope '*/*manual-consortium' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:plugin-cc-tx-visualization": "lerna exec --stream --scope '*/*cc-tx-visualization' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:example-supply-chain-backend": "lerna exec --stream --scope '*/*example-supply-chain-b*' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --display-modules --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:example-carbon-accounting-backend": "lerna exec --stream --scope '*/*carbon-accounting-b*' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --display-modules --env=dev --target=node --config ../../webpack.config.js' && cp -r examples/cactus-example-carbon-accounting-backend/src/utility-emissions-channel/ examples/cactus-example-carbon-accounting-backend/dist/lib/", + "build:dev:sdk": "lerna exec --stream --scope '*/*sdk' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:plugin-ledger-connector-corda": "lerna exec --stream --scope '*/*connector-corda' -- 'del-cli dist/** && npm run tsc && webpack --env=dev --target=node --config ../../webpack.config.js'", + "test:plugin-ledger-connector-corda": "tap --ts --jobs=1 --timeout=600 \"packages/cactus-*-corda/src/test/typescript/{unit,integration}/\"", "webpack": "lerna run webpack:dev", "webpack:dev:web": "lerna run webpack:dev:web", "webpack:dev:node": "lerna run webpack:dev:node", diff --git a/packages/cactus-plugin-cc-tx-visualization/.gitignore b/packages/cactus-plugin-cc-tx-visualization/.gitignore new file mode 100644 index 0000000000..a3a215c6f3 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/.gitignore @@ -0,0 +1,4 @@ +cactus-openapi-spec-plugin-consortium-manual.json +src/main/typescript/generated/openapi/typescript-axios/.npmignore +src/test/csv +src/test/test-results/*.out diff --git a/packages/cactus-plugin-cc-tx-visualization/README.md b/packages/cactus-plugin-cc-tx-visualization/README.md new file mode 100644 index 0000000000..20f5ba92cd --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/README.md @@ -0,0 +1,3 @@ +# `@hyperledger/cactus-plugin-cctxviz` + +The proposed plugin allows generating process models from arbitrary cross-chain use cases. Currently supports Fabric and Besu. More documentation to come soon. diff --git a/packages/cactus-plugin-cc-tx-visualization/package.json b/packages/cactus-plugin-cc-tx-visualization/package.json new file mode 100644 index 0000000000..2f04aafc45 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/package.json @@ -0,0 +1,92 @@ +{ + "name": "@hyperledger/cactus-plugin-cc-tx-visualization", + "version": "1.0.0", + "description": "A web service plugin that provides management capabilities on cross-chain transactions visualization.", + "main": "dist/lib/main/typescript/index.js", + "mainMinified": "dist/cactus-plugin-cc-tx-visualization.node.umd.min.js", + "browser": "dist/cactus-plugin-cc-tx-visualization.web.umd.js", + "browserMinified": "dist/cactus-plugin-cc-tx-visualization.web.umd.min.js", + "module": "dist/lib/main/typescript/index.js", + "types": "dist/types/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev webpack:prod", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:prod": "npm-run-all webpack:prod:node webpack:prod:web", + "webpack:prod:web": "webpack --env=prod --target=web --config ../../webpack.config.js", + "webpack:prod:node": "webpack --env=prod --target=node --config ../../webpack.config.js" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cactus.git" + }, + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "author": { + "name": "Hyperledger Cactus Contributors", + "email": "cactus@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cactus" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Iulia Mihaiu" + }, + { + "name": "Sabrina Scuri" + }, + { + "name": "Rafael Belchior" + } + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hyperledger/cactus/issues" + }, + "homepage": "https://github.com/hyperledger/cactus#readme", + "dependencies": { + "@hyperledger/cactus-common": "1.0.0", + "@hyperledger/cactus-core": "1.0.0", + "@hyperledger/cactus-core-api": "1.0.0", + "@hyperledger/cactus-plugin-ledger-connector-fabric": "1.0.0", + "@hyperledger/cactus-plugin-ledger-connector-besu": "1.0.0", + "amqp-ts": "1.8.0", + "axios": "0.21.1", + "body-parser": "1.19.0", + "csv-stringify": "6.0.5", + "express": "4.17.1", + "fabric-contract-api": "2.2.3", + "jose": "1.28.1", + "json-stable-stringify": "1.0.1", + "prom-client": "13.0.0", + "typescript-optional": "2.0.1", + "uuid": "8.3.2" + }, + "devDependencies": { + "@hyperledger/cactus-test-tooling": "1.0.0", + "@types/express": "4.17.8", + "@types/json-stable-stringify": "1.0.32", + "@types/uuid": "8.3.0" + } +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.web.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.web.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/carbon-footprint.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/carbon-footprint.ts new file mode 100644 index 0000000000..376c2c2292 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/carbon-footprint.ts @@ -0,0 +1,55 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; + +export function calculateGasPriceBesu(gasUsed: number): number { + if (!gasUsed) { + return 0; + } + return getGasPrice() * gasUsed; +} + +// The conversion rate gwei-dollar can be dynamic +export function gweiToDollar(gwei: number): number | string { + if (!gwei) { + return 0; + } + return (gwei * 0.00000255).toFixed(2); +} + +// This value can be retrieved dynamically; 1 gas = 30 gwei +export function getGasPrice(): number { + return 30; +} +// price per gwei +export function gasToDollar(gwei: number): number { + return gwei * 0.005899; +} + +export function calculateCarbonFootPrintFabric( + peers: string[] | undefined, +): number { + if (!peers) { + return 0; + } + return peers.length * CarbonFootPrintConstants(LedgerType.Fabric2); +} +export function calculateCarbonFootPrintBesu(): number { + return CarbonFootPrintConstants(LedgerType.Besu2X); +} + +export const CarbonFootPrintConstants = (ledger: LedgerType): number => { + switch (ledger) { + case LedgerType.Besu2X: + return 0.00018; + + case LedgerType.Besu1X: + return 0.00018; + + case LedgerType.Fabric2: + return 0.00018; + + case LedgerType.Fabric14X: + return 0.00018; + default: + return 0; + } +}; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/cross-chain-event.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/cross-chain-event.ts new file mode 100644 index 0000000000..c9ee7b19e6 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/cross-chain-event.ts @@ -0,0 +1,73 @@ +export type CrossChainEvent = { + caseID: string; + receiptID: string; + timestamp: Date; + blockchainID: string; + invocationType: string; + methodName: string; + parameters: string[]; + identity: string; + cost?: number | string; + carbonFootprint?: number | string; + latency?: number; + revenue?: number; +}; + +export interface ICrossChainEventLog { + name: string; +} + +export class CrossChainEventLog { + private crossChainEvents: CrossChainEvent[] = []; + private creationDate: Date; + private lastUpdateDate: Date; + public readonly logName: string; + //TODO: add a pause boolean? + + constructor(options: ICrossChainEventLog) { + this.creationDate = new Date(); + this.lastUpdateDate = new Date(); + this.logName = options.name; + } + + get logEntries(): CrossChainEvent[] { + return this.crossChainEvents; + } + + public numberEvents(): number { + return this.crossChainEvents.length; + } + public getCreationDate(): Date { + return this.creationDate; + } + + public getLastUpdateDate(): Date { + return this.lastUpdateDate; + } + + public purgeLogs(): void { + this.crossChainEvents = []; + } + + public addCrossChainEvent(event: CrossChainEvent): void { + this.crossChainEvents.push(event); + this.lastUpdateDate = new Date(); + } + + public getCrossChainLogAttributes(): string[] { + return [ + "caseID", + "receiptID", + "timestamp", + "blockchainID", + "invocationType", + "methodName", + "parameters", + "identity", + "cost", + "carbonFootprint", + "latency", + "revenue", + ]; + } +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/crosschain-model.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/crosschain-model.ts new file mode 100644 index 0000000000..5241771d0e --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/crosschain-model.ts @@ -0,0 +1,74 @@ +import { v4 as uuidv4 } from "uuid"; + +export class CrossChainModel { + private modelType: CrossChainModelType | undefined; + private crossChainTransactions: + | Map + | undefined; + private models = new Map(); + private id: string; + private lastAggregationDate: Date; + + constructor() { + this.id = uuidv4(); + this.crossChainTransactions = new Map< + string, + CrossChainTransactionSchema + >(); + this.lastAggregationDate = new Date(); + } + get lastAggregation(): Date { + return this.lastAggregationDate; + } + + public setLastAggregationDate(date: Date): void { + this.lastAggregationDate = date; + } + + public saveModel(type: CrossChainModelType, model: string): void { + this.models.set(type, model); + } + + public getModel(type: CrossChainModelType): string | undefined { + if (this.models.has(type)) { + return this.models.get(type); + } + } + + public getOneCCTx(txKey: string): CrossChainTransactionSchema | undefined { + if (this.crossChainTransactions && this.crossChainTransactions.has(txKey)) { + return this.crossChainTransactions.get(txKey); + } + } + + public getCCTxs(): Map | undefined { + if (this.crossChainTransactions) { + return this.crossChainTransactions; + } + } + + public setCCTxs( + key: string, + mapDefintion: CrossChainTransactionSchema, + ): void { + this.crossChainTransactions?.set(key, mapDefintion); + } +} + +export enum CrossChainModelType { + HeuristicMiner, + ProcessTree, + DirectFollowGraph, +} + +export type CrossChainTransactionSchema = { + ccTxID: string; + // the receipt ids of each cross chain event + processedCrossChainEvents: string[]; + latency: number; + carbonFootprint: number | undefined; + cost: number | undefined; + throughput: number; + latestUpdate: Date; + revenue: number; +}; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/transaction-receipt.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/transaction-receipt.ts new file mode 100644 index 0000000000..0b6b173359 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/models/transaction-receipt.ts @@ -0,0 +1,68 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; +import { Web3SigningCredential } from "@hyperledger/cactus-plugin-ledger-connector-besu/src/main/typescript/public-api"; +import { + FabricSigningCredential, + GatewayOptions, + TransactReceiptBlockMetaData, + TransactReceiptTransactionCreator, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api"; + +export interface TransactionReceipt { + caseID: string; + blockchainID: LedgerType; + invocationType: string; + methodName: string; + parameters: string[]; + timestamp: Date; +} + +export interface IsVisualizable { + // list of transaction receipts, that will be sent to cc-tx-viz + collectTransactionReceipts: boolean; +} + +// TODO define Tx Receipt for Fabric +export interface FabricV2TxReceipt extends TransactionReceipt { + channelName: string; + transactionID: string | undefined; + contractName: string; + endorsingPeers?: string[]; + endorsingParties?: string[]; + transientData?: any | null; + gatewayOptions?: GatewayOptions; + signingCredentials: FabricSigningCredential; + blockNumber?: string; + transactionCreator?: TransactReceiptTransactionCreator; + blockMetaData?: TransactReceiptBlockMetaData; + chainCodeName?: string; + chainCodeVersion?: string; + responseStatus?: string; +} +export interface BesuV2TxReceipt extends TransactionReceipt { + status: boolean; + transactionHash: string; + transactionIndex: number; + blockNumber: number; + blockHash: string; + contractName: string; + contractAddress?: string; + contractAbi?: string[]; + value?: number | string; + gas?: number | string; + gasPrice?: number | string; + gasUsed?: number | string; + cumulativeGasUsed?: number | string; + from: string; + to: string; + signingCredentials?: Web3SigningCredential; + keychainID?: string; + privateTransactionConfig?: string[]; + timeoutMs?: number | string; +} + +export function toSeconds(date: number): number { + return Math.floor(date / 1000); +} +export function millisecondsLatency(date: Date): number { + return new Date().getTime() - date.getTime(); +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-cc-tx-visualization.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-cc-tx-visualization.ts new file mode 100644 index 0000000000..e16888aec1 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-cc-tx-visualization.ts @@ -0,0 +1,416 @@ +/* eslint-disable prettier/prettier */ +import { Server } from "http"; +import { Server as SecureServer } from "https"; +import { Optional } from "typescript-optional"; +import { promisify } from "util"; +import { + IPluginWebService, + IWebServiceEndpoint, + ICactusPlugin, + ICactusPluginOptions, + LedgerType, +} from "@hyperledger/cactus-core-api"; +//import { BesuApiClient} from "@hyperledger/cactus-plugin-ledger-connector-besu/src/main/typescript/public-api"; +import { stringify } from 'csv-stringify'; + +import fs from 'fs'; +import path from 'path'; + +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { Express } from "express"; + +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { calculateGasPriceBesu, CarbonFootPrintConstants, gweiToDollar } from "./models/carbon-footprint"; +import { CrossChainEvent, CrossChainEventLog } from "./models/cross-chain-event"; + +export interface IWebAppOptions { + port: number; + hostname: string; +} +import * as Amqp from "amqp-ts"; +import { CrossChainModel, CrossChainModelType, CrossChainTransactionSchema } from "./models/crosschain-model"; +import { BesuV2TxReceipt, FabricV2TxReceipt, millisecondsLatency } from "./models/transaction-receipt"; +import { randomUUID } from "crypto"; + +export interface IChannelOptions { + queueId: string, + dltTechnology: LedgerType | null, + persistMessages: boolean +} + +export type APIConfig = { + type:LedgerType, + basePath: string +} + +export interface IPluginCcTxVisualizationOptions extends ICactusPluginOptions { + connectorRegistry?: PluginRegistry; + logLevel?: LogLevelDesc; + webAppOptions?: IWebAppOptions; + eventProvider: string; + channelOptions: IChannelOptions; + instanceId: string; +} + +// TODO - for extensability, modularity, and flexibility, +// this plugin could have a list of connections and list of queues +export class CcTxVisualization + implements ICactusPlugin, IPluginWebService { + private readonly log: Logger; + private readonly instanceId: string; + private endpoints: IWebServiceEndpoint[] | undefined; + private httpServer: Server | SecureServer | null = null; + private crossChainLog: CrossChainEventLog; + private crossChainModel: CrossChainModel; + private readonly eventProvider: string; + private amqpConnection: Amqp.Connection; + private amqpQueue: Amqp.Queue; + private amqpExchange: Amqp.Exchange; + public readonly className = "plugin-cc-tx-visualization"; + private readonly queueId: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private txReceipts: any[]; + private readonly persistMessages: boolean; + + constructor(public readonly options: IPluginCcTxVisualizationOptions) { + const startTime = new Date(); + const fnTag = `PluginCcTxVisualization#constructor()`; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.queueId = options.channelOptions.queueId || "cc-tx-viz-queue"; + this.log = LoggerProvider.getOrCreate({ + label: label, + level: level, + }); + this.instanceId = this.options.instanceId; + this.crossChainLog = new CrossChainEventLog({name:"CC-TX-VIZ_EVENT_LOGS"}); + //todo should allow different models to be instantiated + this.crossChainModel = new CrossChainModel(); + this.txReceipts = []; + this.persistMessages = options.channelOptions.persistMessages || false; + this.eventProvider = options.eventProvider; + this.log.debug("Initializing connection to RabbitMQ"); + this.amqpConnection = new Amqp.Connection(this.eventProvider); + this.log.info("Connection to RabbitMQ server initialized"); + this.amqpExchange = this.amqpConnection.declareExchange(`cc-tx-viz-exchange`, "direct", {durable: this.persistMessages}); + this.amqpQueue = this.amqpConnection.declareQueue(this.queueId, {durable: this.persistMessages}); + this.amqpQueue.bind(this.amqpExchange); + + const finalTime = new Date(); + this.log.debug(`EVAL-${this.className}-SETUP-CONSTRUCTOR:${finalTime.getTime()-startTime.getTime()}`); + + } + getOpenApiSpec(): unknown { + throw new Error("Method not implemented."); + } + + get numberEventsLog(): number { + return this.crossChainLog.numberEvents(); + } + + get numberUnprocessedReceipts(): number { + return this.txReceipts.length; + } + + public purgeCrossChainEvents(): void { + this.crossChainLog.purgeLogs(); + } + + // todo connection closing is problematic, tests are left hanging + public async closeConnection(): Promise { + this.log.debug("Closing Amqp connection"); + try { + this.amqpConnection.close(); + this.log.debug(" Amqp connection closed"); + + } catch (error) { + this.log.error(error); + } + + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async onPluginInit(): Promise { + return; + } + + public async shutdown(): Promise { + this.log.info(`Shutting down...`); + const serverMaybe = this.getHttpServer(); + if (serverMaybe.isPresent()) { + this.log.info(`Awaiting server.close() ...`); + const server = serverMaybe.get(); + await promisify(server.close.bind(server))(); + this.log.info(`server.close() OK`); + } else { + this.log.info(`No HTTP server found, skipping...`); + } + } + + + async registerWebServices(app: Express): Promise { + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + + const { log } = this; + + log.info(`Installing web services for plugin ${this.getPackageName()}...`); + + const endpoints: IWebServiceEndpoint[] = []; + + // TODO implement endpoints + + const pkg = this.getPackageName(); + log.info(`Installed web services for plugin ${pkg} OK`, { endpoints }); + + return endpoints; + } + + public getHttpServer(): Optional { + return Optional.ofNullable(this.httpServer); + } + + public getPackageName(): string { + return `@hyperledger/cactus-plugin-cc-tx-visualization`; + } + + public pollTxReceipts(): Promise { + const fnTag = `${this.className}#pollTxReceipts()`; + this.log.debug(fnTag); + return this.amqpQueue.activateConsumer( (message) => { + const messageContent = message.getContent(); + this.log.debug(`Received message from ${this.queueId}: ${message.content.toString()}`); + this.txReceipts.push(messageContent); + message.ack(); + }, { noAck: false }); + } + + // Precion minimum is 4ms by convention + public async hasProcessedXMessages(numberMessages: number, precision: number): Promise { + while (this.txReceipts.length < numberMessages) { + await new Promise((resolve) => setTimeout(resolve, precision)); + } + return; + + } + + public async txReceiptToCrossChainEventLogEntry(): Promise { + const startTime = new Date(); + const fnTag = `${this.className}#pollTxReceipts()`; + this.log.debug(fnTag); + // We are processing receipts to update the CrossChainLog. + // At the end of the processing, we need to clear the transaction receipts that have been processed + // Therefore, we need a listen method that cctxviz is always running, doing polls every X seconds, followed by receipt processing (this method) + try { + this.txReceipts.forEach(receipt => { + switch(receipt.blockchainID) { + case LedgerType.Besu2X: + const besuReceipt: BesuV2TxReceipt = receipt; + const ccEventFromBesu:CrossChainEvent = { + caseID: besuReceipt.caseID, + receiptID: besuReceipt.transactionHash, + blockchainID:besuReceipt.blockchainID, + invocationType: besuReceipt.invocationType, + methodName:besuReceipt.methodName, + parameters:besuReceipt.parameters, + timestamp: besuReceipt.timestamp, + identity: besuReceipt.from, + cost: gweiToDollar(calculateGasPriceBesu(besuReceipt.gasUsed as number)), + carbonFootprint: CarbonFootPrintConstants(LedgerType.Besu2X), + latency: millisecondsLatency(new Date(receipt.timestamp)), + revenue: receipt.revenue || 0, + }; + this.crossChainLog.addCrossChainEvent(ccEventFromBesu); + this.log.info("Added Cross Chain event from BESU"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventFromBesu)}`); + break; + case LedgerType.Fabric2: + const fabricReceipt: FabricV2TxReceipt = receipt; + const ccEventFromFabric:CrossChainEvent = { + caseID: fabricReceipt.caseID, + receiptID: fabricReceipt.transactionID || `FABRIC-CALL-${randomUUID()}`, + blockchainID: fabricReceipt.blockchainID, + invocationType: fabricReceipt.invocationType, + methodName: fabricReceipt.methodName, + parameters: fabricReceipt.parameters, + timestamp: fabricReceipt.timestamp, + identity: fabricReceipt.signingCredentials.keychainRef, + cost: receipt.cost || 0, + carbonFootprint: CarbonFootPrintConstants(LedgerType.Fabric2), + latency: millisecondsLatency(new Date(receipt.timestamp)), + revenue: receipt.revenue || 0, + }; + this.crossChainLog.addCrossChainEvent(ccEventFromFabric); + this.log.info("Added Cross Chain event from FABRIC"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventFromFabric)}`); + break; + // used to test cctxviz + case "TEST": + const ccEventTest:CrossChainEvent = { + caseID: receipt.caseID, + receiptID: receipt.receiptID || randomUUID(), + blockchainID: receipt.blockchainID, + invocationType: receipt.invocationType, + methodName: receipt.methodName, + parameters: receipt.parameters, + timestamp: receipt.timestamp, + identity: receipt.identity, + cost: receipt.cost || 0, + carbonFootprint: receipt.carbonFootprint || 0, + latency: receipt.latency || millisecondsLatency(new Date(receipt.timestamp)), + revenue: receipt.revenue, + }; + this.crossChainLog.addCrossChainEvent(ccEventTest); + this.log.info("Added Cross Chain event TEST"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventTest)}`); + break; + default: + this.log.warn(`Tx Receipt with case ID ${receipt.caseID} is not supported`); + break; + } + + }); + // Clear receipt array + this.txReceipts = []; + const finalTime = new Date(); + this.log.debug(`EVAL-${this.className}-RECEIPT2EVENT:${finalTime.getTime()-startTime.getTime()}`); + return; + } catch (error) { + this.log.error(error); + } + + } + + // Parses the cross chain event log and updates the cross chain model + // This is part of the cc model; have a set that maps case id to data structure; this data structure are the consolidated metrics for a cctx, stores each txid + // run over cc log; if case id is unique create new entry, otherwise add tx to cctx, update metrics, update last update; this is an updatable model + public async aggregateCcTx(): Promise { + const startTime = new Date(); + const lastAggregated = this.crossChainModel.lastAggregation; + const newAggregationDate = new Date(); + const ccTxSet = this.crossChainModel.getCCTxs(); + const logEntries = this.crossChainLog.logEntries; + // If entries are more recent than aggregation + let metrics: CrossChainTransactionSchema = { + ccTxID: "", + processedCrossChainEvents: [], + latency: 0, + carbonFootprint: 0, + cost: 0, + throughput: 0, + latestUpdate: newAggregationDate, + revenue: 0, + }; + const logsToAggregate = logEntries.filter(log => new Date(log.timestamp).getTime() > new Date(lastAggregated).getTime()); + if (logsToAggregate.length === 0) { + const finalTime = new Date(); + + this.log.debug(`EVAL-${this.className}-AGGREGATE-CCTX-NO_NEW_LOGS:${finalTime.getTime()-startTime.getTime()}`); + return;} + logsToAggregate.forEach(eventEntry => { + const key = eventEntry.caseID; + const eventID = eventEntry.receiptID; + let latency = eventEntry.latency as number; + let carbonFootprint = eventEntry.carbonFootprint as number; + let cost = eventEntry.cost as number; + const revenue = eventEntry.revenue as number; + + if (!latency) {latency = 0;} + if (!carbonFootprint) {carbonFootprint = 0;} + if (!cost) {cost = 0;} + if (ccTxSet?.has(key)) { + const existingCCTx = ccTxSet.get(key); + const previousEvents = existingCCTx?.processedCrossChainEvents || []; + const numberOfCurrentEvents = previousEvents.length + 1; + const previousLatency = existingCCTx?.latency || 0; + const previousCarbonFootprint = existingCCTx?.carbonFootprint || 0; + const previousCost = existingCCTx?.cost || 0; + const currentCost = (cost + previousCost) / numberOfCurrentEvents; + const previousRevenue = existingCCTx?.revenue || 0; + const currentRevenue = (revenue + previousRevenue) / numberOfCurrentEvents; + + const updatedMetrics = { + ccTxID: key, + processedCrossChainEvents: [...previousEvents , eventID], + latency: (latency + previousLatency) / numberOfCurrentEvents, + carbonFootprint: (carbonFootprint + previousCarbonFootprint) / numberOfCurrentEvents, + cost: currentCost, + throughput: Number(latency != 0 ? (1 / ((latency + previousLatency) / numberOfCurrentEvents)).toFixed(3) as unknown as number : 0), + latestUpdate: lastAggregated, + revenue: currentRevenue, + }; + this.crossChainModel.setCCTxs(key,updatedMetrics); + } else { + metrics = { + ccTxID: key, + processedCrossChainEvents: [eventID], + latency: latency, + carbonFootprint: carbonFootprint, + cost: cost, + throughput: Number((latency != 0 ? 1 / latency : 0).toFixed(3) as unknown as number), + latestUpdate: lastAggregated, + revenue: revenue, + }; + this.crossChainModel.setCCTxs(key,metrics); + } + }); + this.crossChainModel.setLastAggregationDate(newAggregationDate); + const finalTime = new Date(); + this.log.debug(`${this.className}-AGGREGATE-CCTX-SUCCESS:${finalTime.getTime()-startTime.getTime()}`); + return; + } + + public async persistCrossChainLogCsv (name?: string): Promise { + const startTime = new Date(); + const columns = this.crossChainLog.getCrossChainLogAttributes(); + const logName = name? `${name}.csv` : `cctxviz_log_${new Date().getTime()}.csv`; + const csvFolder = path.join(__dirname, "../" , "../", "test", "csv"); + const logPath = path.join(csvFolder , logName); + + stringify( + this.crossChainLog.logEntries + , { + header: true, + columns: columns, + delimiter: ";", + }, (err, data) =>{ + if (err) { + this.log.error(err); + throw new Error("failed to stringify log"); + } + this.log.debug(data); + fs.writeFileSync(logPath, data); + }); + const finalTime = new Date(); + this.log.debug(`EVAL-${this.className}-PERSIST-LOG:${finalTime.getTime()-startTime.getTime()}`); + return logName; + } + + // Receives a serialized model + public async saveModel (modelType: CrossChainModelType, model :string): Promise { + this.crossChainModel.saveModel(modelType, model); + } + + public async getModel (modelType: CrossChainModelType): Promise { + return this.crossChainModel.getModel(modelType); + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-factory-cc-tx-visualization.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-factory-cc-tx-visualization.ts new file mode 100644 index 0000000000..7d18d6d1c1 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/plugin-factory-cc-tx-visualization.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginCcTxVisualizationOptions, + CcTxVisualization, +} from "./plugin-cc-tx-visualization"; + +export class PluginFactoryWebService extends PluginFactory< + CcTxVisualization, + IPluginCcTxVisualizationOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginCcTxVisualizationOptions, + ): Promise { + return new CcTxVisualization(pluginOptions); + } +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/public-api.ts b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..7da664906e --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/main/typescript/public-api.ts @@ -0,0 +1,17 @@ +export { + CcTxVisualization, + IPluginCcTxVisualizationOptions, + IWebAppOptions, +} from "./plugin-cc-tx-visualization"; + +export { IsVisualizable } from "./models/transaction-receipt"; +export { PluginFactoryWebService } from "./plugin-factory-cc-tx-visualization"; + +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +import { PluginFactoryWebService } from "./plugin-factory-cc-tx-visualization"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryWebService(pluginFactoryOptions); +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-6-events.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-6-events.csv new file mode 100644 index 0000000000..0b0ec28b6c --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-6-events.csv @@ -0,0 +1,7 @@ +caseID;receiptID;timestamp;blockchainID;invocationType;methodName;parameters;identity;cost;carbonFootprint;latency;revenue +1;515ca87f-fbac-49f0-a4cf-d3bf5eb62860;2022-08-03T14:04:52.844Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7621; +1;2a5ca7a6-344c-4dfd-a005-98811fc889ce;2022-08-03T14:04:52.846Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7620; +1;4b3a5b7f-9bb2-413e-b2f4-74822db34c07;2022-08-03T14:04:52.847Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7619; +1;ee00b057-2f8e-4d57-89b0-ec9390a53e9b;2022-08-03T14:04:52.848Z;TEST;send;transfer asset;"[""A""]";A;0;0;7618; +1;d31efa25-097b-4496-9c75-bc8daca1cf51;2022-08-03T14:04:52.849Z;TEST;send;transfer asset;"[""""]";A;0;0;7617; +1;020c5f54-006f-4f49-b675-7cf85fb3cd80;2022-08-03T14:04:52.850Z;TEST;send;burn asset;"[""""]";A;0;0;7617; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-60-events.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-60-events.csv new file mode 100644 index 0000000000..378c12bc6d --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-60-events.csv @@ -0,0 +1,61 @@ +caseID;receiptID;timestamp;blockchainID;invocationType;methodName;parameters;identity;cost;carbonFootprint;latency;revenue +10;672c7375-b463-4602-882a-5b50ca93af39;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7646; +10;3095c26a-d2db-4f25-823e-0de1e829e11d;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7644; +10;5c2c87f0-0f27-4e86-85b2-8b0816b10c24;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7643; +10;022e0884-4b78-4784-8929-caa2d1ed7234;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7642; +10;af311552-9ff1-421c-a4f1-d12e5ec20347;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7642; +10;7af1d933-da3c-4835-bc96-4dbbac913ddf;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7641; +9;ddcaff66-949e-49cb-9250-508598fc4408;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7647; +9;9f0eb346-4bfc-4687-8ca6-2fc181260eb0;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7645; +9;33ed7788-50e1-44a4-8d5a-62656dd84cf7;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7644; +9;564c7787-eae0-4dbe-82c7-4a2f07a65286;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7644; +9;66d2cd2c-b5ee-417c-a09f-4910a42468ed;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7643; +9;24a76769-c283-4ed3-a6c1-f495a068a94f;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7642; +8;96b7c76f-7f2a-444f-a263-331292842dd3;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7648; +8;06830bf5-d41c-4dce-aea3-237f0ba7ed71;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7647; +8;5429fead-c8f7-4906-a15b-1d0ff84885d1;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7646; +8;d4316180-80af-4eb0-91bc-e3a7c302b3f2;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7645; +8;552525b6-8ec6-4b65-a8ce-0afc245dadf4;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7644; +8;df8de589-e4c1-4323-b283-088dd60368a6;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7643; +7;5974b1e7-5db3-4139-ab8a-d9e255ea72a4;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7649; +7;39d7a374-4725-48cf-a600-4d0935b7e249;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7648; +7;0fbff6f5-c1a4-470f-a957-d98086e51cbf;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7647; +7;054cbc90-06cd-4461-8509-903fca7fc963;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7646; +7;abbd93cf-9ea2-431d-b688-4f85d0befa1d;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7645; +7;432d3e06-9624-438f-99c3-173452359762;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7645; +6;d022a95a-bf69-4585-8d2c-c1d09c8d2ab7;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7651; +6;40be8287-6f8c-4719-a24d-3270d07682e4;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7649; +6;0c9e2c65-e2ac-4d47-8fab-35029aaf86d7;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7648; +6;125cccb6-56cb-4653-8114-609d29bfced0;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7647; +6;adf7b151-d318-4ddd-9c31-8a0e9319eb88;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7646; +6;29c7f1a7-c812-4c4c-bb55-90ae0c78eaed;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7646; +5;09dfa0a0-1107-4453-8cb4-dcca500b102e;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7652; +5;c81ffbcd-7bdf-44c3-bc9b-a3d664464094;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7650; +5;7e78e8ea-912e-471b-b412-2b2a71c2bdc7;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7649; +5;71587b9e-78d8-4f8f-a3df-568d26dded51;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7648; +5;127b0e58-cf75-4dbe-9767-8053335f90d2;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7648; +5;4173755f-6012-4e14-9b5c-52f628888846;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7647; +4;2be88040-b17d-4e71-854e-7b815778713e;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7653; +4;550643ce-7e2c-4f6a-bed7-6400fa8ba5be;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7651; +4;85b72b5e-c6f0-4d53-8527-8283e0b198a0;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7650; +4;7fb7df34-c7c0-4077-bffa-577dbd11e9ea;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7649; +4;bb1b5e57-bfa9-407b-8797-b64f0e99ec81;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7649; +4;1583ef47-dff0-4897-a9a2-1fddb02817ab;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7648; +3;07803bdd-6f8b-460b-9e4e-378befce11e2;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7654; +3;fb1d93d3-9e98-4213-bf3d-e219d0201fd3;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7652; +3;6ec5ed7d-fad5-41c1-96b7-834370dfddd5;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7651; +3;aa32d8bf-9a17-479b-be99-098931378896;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7651; +3;961f131b-9632-4909-ad73-a30c97d689c8;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7650; +3;e7d9e88c-5a8b-4ae5-aeb3-16bc7fe10d99;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7649; +2;2cdc5e7c-1ce0-43f2-a6a5-cac754beba08;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7655; +2;f48ff1d5-5f9d-4769-93dc-7435f516d3e3;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7653; +2;e4d51446-a241-47f7-932e-187102aca449;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7653; +2;2aa46d3c-111c-4b5b-80e1-5716ba4a889c;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7652; +2;cad9d38d-5791-445f-a7ae-7bad0dea5525;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7651; +2;086a681d-3c3c-49ea-a66c-3251eae97633;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7650; +1;ed54f76d-3ace-45f5-b233-7b541d7deec5;2022-07-07T16:03:18.375Z;TEST;send;initialize asset;"[""1,100""]";A;0;0;7656; +1;bc54f38c-909f-433b-ace9-5acf4c109784;2022-07-07T16:03:18.377Z;TEST;send;lock asset;"[""1,100""]";A;0;0;7655; +1;8006179b-8e93-4ac1-b806-016138c79b7c;2022-07-07T16:03:18.378Z;TEST;send;mint asset;"[""1,100""]";A;0;0;7654; +1;8bfbc22d-2224-4050-b513-205b1529a9d3;2022-07-07T16:03:18.379Z;TEST;send;transfer asset;"[""A""]";A;0;0;7653; +1;a47b4c5e-ed79-423a-a5f0-832af54d356f;2022-07-07T16:03:18.380Z;TEST;send;transfer asset;"[""""]";A;0;0;7652; +1;3aecbfb3-3b9f-43e9-a88d-dc8dc06b0608;2022-07-07T16:03:18.381Z;TEST;send;burn asset;"[""""]";A;0;0;7651; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-invalid.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-invalid.csv new file mode 100644 index 0000000000..11c53ff5f7 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/dummy-use-case-invalid.csv @@ -0,0 +1,7 @@ +caseID;receiptID;timestamp;blockchainID;invocationType;methodName;parameters;identity;cost;carbonFootprint;latency;revenue +INVALID_FABRIC_BESU_1;f0e58801-e6dd-44d4-91de-4adb2113a68b;2022-08-03T14:05:37.740Z;TEST;send;createAsset;"[""asset1,5""]";A;0;0;7602; +INVALID_FABRIC_BESU_1;fe6e7084-74da-4d1e-8681-6603111a71fc;2022-08-03T14:05:37.742Z;TEST;send;mintAsset;"[""asset1"",""Green"",""19"",""owner1"",""9999""]";A;0;0;7601; +INVALID_FABRIC_BESU_1;e7466cb2-4eaf-44f0-acd3-0b0dfae52801;2022-08-03T14:05:37.743Z;TEST;send;lockAsset;"[""asset1""]";A;0;0;7600; +INVALID_FABRIC_BESU_1;4df9be1c-6e47-40dd-8722-90072b0b04bd;2022-08-03T14:05:37.744Z;TEST;send;transferAsset;"[""asset1"",""owner2""]";A;0;0;7599; +INVALID_FABRIC_BESU_1;9a130336-c165-4e05-b72c-3b9c3fa583a0;2022-08-03T14:05:37.745Z;TEST;send;transferAsset;"[""asset1"",""owner1""]";A;0;0;7598; +INVALID_FABRIC_BESU_1;b8fef379-e64f-4aa0-97d8-45f28276576c;2022-08-03T14:05:37.746Z;TEST;send;BurnAsset;"[""asset1""]";A;0;0;7598; diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/example-dummy-use-case.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/example-dummy-use-case.csv new file mode 100644 index 0000000000..a4e72b6acb --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/example-dummy-use-case.csv @@ -0,0 +1,9 @@ +caseID;timestamp;blockchainID;invocationType;methodName;parameters;identity +1;2022-04-28T16:14:23.922Z;TEST;send;registerEmission;"[""1,100""]";company_A +2;2022-04-28T16:14:23.922Z;TEST;send;registerEmission;"[""2,100""]";company_B +1;2022-04-29T20:01:03.922Z;TEST;send;registerEmission;"[""2,100""]";company_A +1;2022-05-10T06:01:03.922Z;TEST;send;getEmissions;"[""company_A""]";auditor +1;2022-05-11T09:47:43.922Z;TEST;send;mintEmissionToken;"[""uuidTokenEmissions""]";protocol_administrator +2;2022-04-29T22:47:43.922Z;TEST;send;registerEmission;"[""2,100""]";company_B +2;2022-05-11T09:47:43.922Z;TEST;send;getEmissions;"[""company_B""]";auditor +2;2022-05-12T13:34:23.922Z;TEST;send;mintEmissionToken;"[""uuidTokenEmissions""]";protocol_administrator diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-6-events.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-6-events.csv new file mode 100644 index 0000000000..2619033017 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-6-events.csv @@ -0,0 +1,7 @@ +caseID;receiptID;timestamp;blockchainID;invocationType;methodName;parameters;identity;cost;carbonFootprint;latency;revenue +FABRIC_BESU;0xd184f5a49d170e2f7f9fcb7996e8db3d7764c13d798fc5a795e0d9371dd61435;2022-06-24T14:47:43.716Z;BESU_2X;SEND;createAsset;"[""asset1"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;10065;0 +FABRIC_BESU;0xc315d4ee310a04d4c0949279f4edba34fa8fb35cf5fe4fb1e4b95488202860fc;2022-06-24T14:47:45.312Z;BESU_2X;SEND;lockAsset;"[""asset1""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;8469;0 +FABRIC_BESU;7fddc8aef1a46f23fa33891b4ab6b951c7224671575b573cd47e7711cd9449b1;2022-06-24T14:47:47.102Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset1"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;6679;0 +FABRIC_BESU;7c395a691ba45d7fe921fb5f69b40fbff810459f8f9f274307c58fb070b2910a;2022-06-24T14:47:49.361Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset1"",""owner2""]";user2;0;0.00018;4420;0 +FABRIC_BESU;a6b747257c6e92b8c85292561999dad7dcf56051f2c6c639a2bbbd987a4afbdf;2022-06-24T14:47:51.573Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset1"",""owner1""]";user2;0;0.00018;2208;0 +FABRIC_BESU;fc20c6cd4f1cbd6b06da6cd1fc846a0b2051382096d63cd7e8a67865471526f1;2022-06-24T14:47:53.767Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset1""]";user2;0;0.00018;14;0 diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-60-events.csv b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-60-events.csv new file mode 100644 index 0000000000..2e6e397e0b --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/csv/use-case-besu-fabric-60-events.csv @@ -0,0 +1,61 @@ +caseID;receiptID;timestamp;blockchainID;invocationType;methodName;parameters;identity;cost;carbonFootprint;latency;revenue +10;0x0b84cd21633da69461dc0189b4ddc566bd4d794cb5b0094348c6d24217c66687;2022-06-25T10:59:25.183Z;BESU_2X;SEND;createAsset;"[""asset10"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.28;0.00018;103484;0 +10;0x255fb16b63d60d9f24f9809e444f0c5da6a1145d9de25c8b8acca5f0791b73bc;2022-06-25T10:59:26.102Z;BESU_2X;SEND;lockAsset;"[""asset10""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;102566;0 +10;1b7572e4c269dc6ee321da24b8eb464d1a6cee310ba3975bd4f90edf414c4766;2022-06-25T10:59:27.347Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset10"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;101321;0 +10;264b1a9e086920b5c8e21562dfd0b7034a313fb1c672d4b0a5805120aacde642;2022-06-25T10:59:29.562Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset10"",""owner2""]";user2;0;0.00018;99106;0 +10;17655a502c1ff999d966b926f99658b8dd84027e3f5b49b6374a162d7ebe39ce;2022-06-25T10:59:31.777Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset10"",""owner1""]";user2;0;0.00018;96892;0 +10;6d173ade6a8da3e9b7b4cdc54a0756d4eeb5a6ecae61edb32c892956f87433bf;2022-06-25T10:59:34.051Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset10""]";user2;0;0.00018;94618;0 +9;0xc27e6a83b344020f5cdffed3ec0e76f85ad1f46bef5874f7237219540626324a;2022-06-25T10:59:35.130Z;BESU_2X;SEND;createAsset;"[""asset9"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;93539;0 +9;0x19c60c14bcc4162e6633f76e0ee12911c8c5a4164a4e84fcdfc5bfa362f4546d;2022-06-25T10:59:36.173Z;BESU_2X;SEND;lockAsset;"[""asset9""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;92497;0 +9;7dc4caed5df3b823497626e64d8251b88ebda89658df4366d2bd85e0629ba542;2022-06-25T10:59:38.453Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset9"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;90217;0 +9;551bfac56479dc5ea1044a081526588ebe272984bfc113edf02cf4c7516d7d39;2022-06-25T10:59:40.676Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset9"",""owner2""]";user2;0;0.00018;87994;0 +9;9b06aaa26a2235df1c83687eb5425ca58e7150f3a6b21b74a5bbac56fb3a768a;2022-06-25T10:59:42.867Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset9"",""owner1""]";user2;0;0.00018;85803;0 +9;e15cdcc70b1bc670b50acf8dd7d074f07e43b49cecce6ef8216e9e25c70856e8;2022-06-25T10:59:45.105Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset9""]";user2;0;0.00018;83566;0 +8;0x9b31a1c51a1498d0cbcbc6e7751ec96847509146f1b0824c72e88e36f28dc675;2022-06-25T10:59:46.224Z;BESU_2X;SEND;createAsset;"[""asset8"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;82447;0 +8;0x7d962f902de9367d477f24f13d106f0de8002f217f401eac16a485d274a03e2a;2022-06-25T10:59:47.386Z;BESU_2X;SEND;lockAsset;"[""asset8""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;81285;0 +8;d5ce85e36492b9c164d88c4ba4615202c20ff7018303ec7855fcbf979aa2038d;2022-06-25T10:59:49.537Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset8"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;79135;0 +8;6f4bacb0a6cc996ebd241a51e73c2b7da61085284f5bb2b7097191324391189a;2022-06-25T10:59:51.679Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset8"",""owner2""]";user2;0;0.00018;76993;0 +8;750c903bd5870a72d6a48836123af036c34bed1ff0f0c427ec7702c9e6d5477d;2022-06-25T10:59:53.840Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset8"",""owner1""]";user2;0;0.00018;74832;0 +8;70f3f9e97cceb4ae4c992c701b75188ffac571ea28bc2a7b664d9d83f7d2d634;2022-06-25T10:59:56.017Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset8""]";user2;0;0.00018;72655;0 +7;0x7c2e2a12a0263a0562f0e874b5367856dff1295b447b6aa6587499520eb483f6;2022-06-25T10:59:57.165Z;BESU_2X;SEND;createAsset;"[""asset7"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;71508;0 +7;0xdd4f0626d4893812a69bbf7369598e8930328bf0a2f72530f5e328a3dab5f23f;2022-06-25T10:59:58.644Z;BESU_2X;SEND;lockAsset;"[""asset7""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;70029;0 +7;aafd581a460cb584394b5b02f158521fece5a64a392764a6c6b64a6144735dd6;2022-06-25T11:00:00.353Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset7"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;68320;0 +7;1dcaef34942fcd520df700e1ddb289940ecd92b48ef422ac8bcc297c816dccc0;2022-06-25T11:00:02.559Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset7"",""owner2""]";user2;0;0.00018;66114;0 +7;3b66f7f62ecbdd4fe41c71f6379b712ad7e3f845b76e75af2f7c08e57aa1011b;2022-06-25T11:00:04.791Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset7"",""owner1""]";user2;0;0.00018;63883;0 +7;99ee4284e38531fc2a4b2524019d48dcd8960d0d784d9c99f47f8514ed77b061;2022-06-25T11:00:06.981Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset7""]";user2;0;0.00018;61693;0 +6;0x2348af3e9cf3e817918c947002ac5c5ce25d1a720c02fc35a90f91cb13bb0ec3;2022-06-25T11:00:07.306Z;BESU_2X;SEND;createAsset;"[""asset6"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;61368;0 +6;0xb257570d87c2025d4a0f5a95a2ed74fdac76ad5329449841ad2347b21d4f3432;2022-06-25T11:00:08.058Z;BESU_2X;SEND;lockAsset;"[""asset6""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;60617;0 +6;0b0aaf2bf5c0f563827bb8560840a9997ad30ef527c2b6ff08de7aea15ef3c7e;2022-06-25T11:00:09.195Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset6"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;59480;0 +6;7f3d17884f04f12f3d0aa514cfbf9c152b445e48e5b7379883c7b11d539460ab;2022-06-25T11:00:11.431Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset6"",""owner2""]";user2;0;0.00018;57244;0 +6;594be967c7b886364cf2187676712bacd40eaa9e71a012bfc51d5af6d1e036e8;2022-06-25T11:00:13.676Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset6"",""owner1""]";user2;0;0.00018;54999;0 +6;c3b4d35769879dfd5b69239b01bc99cb1c24da3ec23b684d9c1e9769e4051fbd;2022-06-25T11:00:15.933Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset6""]";user2;0;0.00018;52743;0 +5;0x4056b743c139167e7520c3b092e506cb0a7bfcb5f01a97ea92cad8193356f063;2022-06-25T11:00:16.066Z;BESU_2X;SEND;createAsset;"[""asset5"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;52610;0 +5;0xc67fcd01535160e32f7b2b1d53cd92708a1bcbf69836000da85539e2e23efa15;2022-06-25T11:00:17.035Z;BESU_2X;SEND;lockAsset;"[""asset5""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;51641;0 +5;42b1100d844150d3223995b6c1baaa94150762de464be17cadee40c186d14b44;2022-06-25T11:00:18.087Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset5"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;50589;0 +5;8a9dd0e172f6b1f76ec9ba19509646b2719036735b713d160224c872e0bfd459;2022-06-25T11:00:20.293Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset5"",""owner2""]";user2;0;0.00018;48383;0 +5;f738b68c0e698d6475df97e8d9d131eb2716cedb0b5ec8ef0725c7678cdb5721;2022-06-25T11:00:22.492Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset5"",""owner1""]";user2;0;0.00018;46185;0 +5;9868694a2d0bfe679138eb43eb3f1fd13443bdd6fa99d4dc1a0a27fc37c76eaa;2022-06-25T11:00:24.713Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset5""]";user2;0;0.00018;43964;0 +4;0x1a674d3f2a7128fd0673ed20bcaa65866ab94e881e6ffe63376d4ab4cd613a39;2022-06-25T11:00:25.504Z;BESU_2X;SEND;createAsset;"[""asset4"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;43173;0 +4;0x5e6d1389a7dab4fe7aa33337e39330d722aec12af775f3d14204c2c3e6e6085a;2022-06-25T11:00:26.032Z;BESU_2X;SEND;lockAsset;"[""asset4""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;42646;0 +4;575bf45fd7e82267451465f7cbba7539f52ede08122839cf7b289e9833618fc0;2022-06-25T11:00:26.921Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset4"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;41757;0 +4;5cea5444721da108ffeac701e33773dde47ac7cf3596463f23b3304c1f85cd51;2022-06-25T11:00:29.124Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset4"",""owner2""]";user2;0;0.00018;39554;0 +4;f453e1de3aee3ac960fa1af1fc53f67f3e14fde5a37c68e30bd0795cdec6d58a;2022-06-25T11:00:31.343Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset4"",""owner1""]";user2;0;0.00018;37336;0 +4;071c140a9278eb6e91ea48988d8baa0eb4bc2bf2e861e11a502a634bc9704f42;2022-06-25T11:00:33.553Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset4""]";user2;0;0.00018;35126;0 +3;0x4365fd66956e3636a770292e7768b204b49c72fe3044171c36877d1d0dbed06b;2022-06-25T11:00:34.209Z;BESU_2X;SEND;createAsset;"[""asset3"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;34470;0 +3;0xcdad311631eaaea0722fff7fee626aff8edd9c1534517da781918277b4fe9c62;2022-06-25T11:00:35.117Z;BESU_2X;SEND;lockAsset;"[""asset3""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;33562;0 +3;5af29303a26443813ab900d7fd28eb4c6c8421333d865ed8c4329545997f79cf;2022-06-25T11:00:35.757Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset3"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;32923;0 +3;6b99476527ac6826128b30f95b9249097f398b38be9062b37dea79e9dced08d4;2022-06-25T11:00:37.979Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset3"",""owner2""]";user2;0;0.00018;30701;0 +3;580e3274acd62a5e4596b3e77308d9ae34b86804950a26bb339cd77a9e90c34b;2022-06-25T11:00:40.169Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset3"",""owner1""]";user2;0;0.00018;28511;0 +3;eb02da68b4ddeab17eae477c1ee4c2b26260d31bd8e15ff8d01dc76a24af8ac0;2022-06-25T11:00:42.330Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset3""]";user2;0;0.00018;26350;0 +2;0x0df56eeaebb034e0c74e7eaac81310536a8f5cb021470f7f596c27e14221c03b;2022-06-25T11:00:43.118Z;BESU_2X;SEND;createAsset;"[""asset2"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;25563;0 +2;0x20d772dffe4ed51f09850544a282032598d0146a4d2b8a6da0334de837b86094;2022-06-25T11:00:44.327Z;BESU_2X;SEND;lockAsset;"[""asset2""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;24354;0 +2;1dec685fc674460e905bc5631934764af97a0a71c4bdaa4cca58f1b1c0097d33;2022-06-25T11:00:46.592Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset2"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;22089;0 +2;0da0bf6a104da6cae5d8e7971a1f05d0c684889043eec86d029108fd385d9ea1;2022-06-25T11:00:48.854Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset2"",""owner2""]";user2;0;0.00018;19827;0 +2;dcce95975672efef29db26bbeffc6a1c5247da4f485168fb581cd308e5589c86;2022-06-25T11:00:51.117Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset2"",""owner1""]";user2;0;0.00018;17565;0 +2;3083629ff79e16f8470e4ea1d8fbc5c9115491fcf6eb7e695e8f7838e0481afb;2022-06-25T11:00:53.342Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset2""]";user2;0;0.00018;15340;0 +1;0x6907cca45d572b05bdf0db922fe58e313e0fdb0df44199ddd96b6005db09e3ae;2022-06-25T11:00:54.159Z;BESU_2X;SEND;createAsset;"[""asset1"",5]";0x627306090abab3a6e1400e9345bc60c78a8bef57;5.27;0.00018;14523;0 +1;0xfbf7a0ef853fd86384adc9b7f67f0cef8e8c68b5745890220ae989538e1946c1;2022-06-25T11:00:55.030Z;BESU_2X;SEND;lockAsset;"[""asset1""]";0x627306090abab3a6e1400e9345bc60c78a8bef57;2.18;0.00018;13652;0 +1;8c6f92d3dde2147ac0632107ad4273106fc73ab46bf340cb6cf7af46e244c67b;2022-06-25T11:00:55.573Z;FABRIC_2;FabricContractInvocationType.SEND;MintAsset;"[""asset1"",""Green"",""19"",""owner1"",""9999""]";user2;0;0.00018;13109;0 +1;4a6ac7d83bb4c5d67b1007688f81a2ff8e0df1792c34db3893eb652c409bcd14;2022-06-25T11:00:57.797Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset1"",""owner2""]";user2;0;0.00018;10886;0 +1;7eb266fae4cf38b200a4f9f3a27d8ba39a1759b1367a0d5b8162f93b58a907e7;2022-06-25T11:01:00.009Z;FABRIC_2;FabricContractInvocationType.SEND;TransferAsset;"[""asset1"",""owner1""]";user2;0;0.00018;8674;0 +1;8a8f26e2aad74625f1dd084ef6fdcb141421b1c17511721350aaff20bcb49dd7;2022-06-25T11:01:02.244Z;FABRIC_2;FabricContractInvocationType.SEND;BurnAsset;"[""asset1""]";user2;0;0.00018;6440;0 diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process mining.ipynb b/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process mining.ipynb new file mode 100644 index 0000000000..e3a085eab6 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process mining.ipynb @@ -0,0 +1,423 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "module 'posixpath' has no attribute 'parents'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;31m# Change path if necessary\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mlevels_up\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparents\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mlevels_up\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0mfile_path\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparent\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"/csv/use-case-besu-fabric-6-events.csv\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0mfile_path_other_model\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparent\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"/csv/dummy-use-case-invalid.csv\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'posixpath' has no attribute 'parents'" + ] + } + ], + "source": [ + "import os\n", + "path = os.getcwd()\n", + "parent = os.path.dirname(path)\n", + "\n", + "\n", + "# Change path if necessary\n", + "file_path = parent + \"/csv/use-case-besu-fabric-6-events.csv\"\n", + "file_path_other_model = parent + \"/csv/dummy-use-case-invalid.csv\"\n", + "\n", + "import sys\n", + "\n", + "if __name__ == '__main__':\n", + " print(sys.argv)\n", + " print(file_path)\n", + "\n", + "\n", + "\n", + "# import sys\n", + "\n", + "# accept command line arguments\n", + "# inputArg1 = sys.argv[1]\n", + "\n", + "#print('inputArg1: ',inputArg1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# uncomment if problems with dependencies\n", + "#%pip install pm4py\n", + "#%pip install pandas\n", + "import pm4py\n", + "import datetime as dt\n", + "import time\n", + "import pandas\n", + "\n", + "# process mining\n", + "from pm4py.algo.discovery.alpha import algorithm as alpha_miner\n", + "from pm4py.algo.discovery.inductive import algorithm as inductive_miner\n", + "from pm4py.algo.discovery.heuristics import algorithm as heuristics_miner\n", + "from pm4py.algo.discovery.dfg import algorithm as dfg_discovery\n", + "\n", + "# viz\n", + "from pm4py.visualization.petri_net import visualizer as pn_visualizer\n", + "from pm4py.visualization.process_tree import visualizer as pt_visualizer\n", + "from pm4py.visualization.heuristics_net import visualizer as hn_visualizer\n", + "from pm4py.visualization.dfg import visualizer as dfg_visualization\n", + "\n", + "from pm4py.objects.conversion.process_tree import converter as pt_converter\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "def import_csv_original(file_path):\n", + " event_log = pandas.read_csv(file_path, sep=';')\n", + " event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp')\n", + " return event_log\n", + "\n", + "def getStartActivities(event_log):\n", + " s = pm4py.get_start_activities(event_log)\n", + " print(\"Start activities: {}\\n\".format(s))\n", + " return s\n", + "def getEndActivities(event_log):\n", + " e = pm4py.get_end_activities(event_log)\n", + " print(\"End activities: {}\\n\".format(e))\n", + " return (e)\n", + "\n", + "def getAttributeFromLog(event_log, attr):\n", + " entries = pm4py.get_event_attribute_values(event_log,attr)\n", + " print(\"Entries: {}\\n\".format(entries))\n", + " return entries" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/rafaelapb/Projects/cactus-with-branches/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/../../csv/use-case-besu-fabric-6-events.csv\n" + ] + }, + { + "ename": "FileNotFoundError", + "evalue": "[Errno 2] No such file or directory: '/Users/rafaelapb/Projects/cactus-with-branches/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/../../csv/use-case-besu-fabric-6-events.csv'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mlog\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimport_csv_original\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mlog_other_model\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mimport_csv_original\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_path_other_model\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlog\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"leght is\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlog\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m\u001b[0m in \u001b[0;36mimport_csv_original\u001b[0;34m(file_path)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mimport_csv_original\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mevent_log\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpandas\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread_csv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msep\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m';'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mevent_log\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpm4py\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat_dataframe\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mevent_log\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcase_id\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'caseID'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mactivity_key\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'methodName'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimestamp_key\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'timestamp'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mevent_log\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36mread_csv\u001b[0;34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, dialect, error_bad_lines, warn_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)\u001b[0m\n\u001b[1;32m 608\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkwds_defaults\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 609\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 610\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_read\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 611\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 612\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36m_read\u001b[0;34m(filepath_or_buffer, kwds)\u001b[0m\n\u001b[1;32m 460\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 461\u001b[0m \u001b[0;31m# Create the parser.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 462\u001b[0;31m \u001b[0mparser\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTextFileReader\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfilepath_or_buffer\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 463\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 464\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mchunksize\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0miterator\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, f, engine, **kwds)\u001b[0m\n\u001b[1;32m 817\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"has_index_names\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"has_index_names\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 818\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 819\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_make_engine\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mengine\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 820\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 821\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36m_make_engine\u001b[0;34m(self, engine)\u001b[0m\n\u001b[1;32m 1048\u001b[0m )\n\u001b[1;32m 1049\u001b[0m \u001b[0;31m# error: Too many arguments for \"ParserBase\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1050\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mmapping\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mengine\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moptions\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# type: ignore[call-arg]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1051\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1052\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_failover_to_python\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, src, **kwds)\u001b[0m\n\u001b[1;32m 1865\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1866\u001b[0m \u001b[0;31m# open handles\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1867\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_open_handles\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msrc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mkwds\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1868\u001b[0m \u001b[0;32massert\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhandles\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1869\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m\"storage_options\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"encoding\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"memory_map\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"compression\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/parsers.py\u001b[0m in \u001b[0;36m_open_handles\u001b[0;34m(self, src, kwds)\u001b[0m\n\u001b[1;32m 1360\u001b[0m \u001b[0mLet\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mreaders\u001b[0m \u001b[0mopen\u001b[0m \u001b[0mIOHanldes\u001b[0m \u001b[0mafter\u001b[0m \u001b[0mthey\u001b[0m \u001b[0mare\u001b[0m \u001b[0mdone\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mtheir\u001b[0m \u001b[0mpotential\u001b[0m \u001b[0mraises\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1361\u001b[0m \"\"\"\n\u001b[0;32m-> 1362\u001b[0;31m self.handles = get_handle(\n\u001b[0m\u001b[1;32m 1363\u001b[0m \u001b[0msrc\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1364\u001b[0m \u001b[0;34m\"r\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/opt/anaconda3/lib/python3.8/site-packages/pandas/io/common.py\u001b[0m in \u001b[0;36mget_handle\u001b[0;34m(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)\u001b[0m\n\u001b[1;32m 640\u001b[0m \u001b[0merrors\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"replace\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 641\u001b[0m \u001b[0;31m# Encoding\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 642\u001b[0;31m handle = open(\n\u001b[0m\u001b[1;32m 643\u001b[0m \u001b[0mhandle\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 644\u001b[0m \u001b[0mioargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmode\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/Users/rafaelapb/Projects/cactus-with-branches/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/../../csv/use-case-besu-fabric-6-events.csv'" + ] + } + ], + "source": [ + "print(file_path)\n", + "log = import_csv_original(file_path)\n", + "log_other_model = import_csv_original(file_path_other_model)\n", + "print(log)\n", + "print(\"leght is\", len(log))\n", + "print(log_other_model)\n", + "print(\"leght is\", len(log_other_model))\n", + "startAct = getStartActivities(log)\n", + "endAct = getEndActivities(log)\n", + "timestamps = getAttributeFromLog(log, \"timestamp\")\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "->( 'createAsset', 'lockAsset', 'MintAsset', *( 'TransferAsset', tau ), 'BurnAsset' )\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Execution Time : 0.001\n", + "Execution Time (ms): 1.402\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABKgAAABZCAYAAAAaXByKAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nO3dd5wU9f3H8ddtOeohEIKIWKLYEEssPzVobARNxA5IYkNjQWMsoLElxp8tloi9/4LYRawxGgu2EDVGY4IoFgg2LChFmnB3uzu/Pz4z7t7e9p292d17P32sx83Mzn73dm7uO5/5fD9fEBERERERERERERERERERERERERERERERERERERERERERERERkc6mIegGiIiIiIhkMSboBkhVeyDoBoiIiH8UoBIRERGRauUE3QCparqWEREREREREZGKc1AWlbQ3BgUvRUTqTijoBoiIiIiIiIiISOemAJWIiIiIiIiIiARKASoREREREREREQmUAlQiIiIiIiIiIhIoBahERERERERERCRQClCJiIiIiIiIiEigFKASEREREREREZFAKUAlIiIiIiIiIiKBUoBKREREREREREQCpQCViIiIiIiIiIgESgEqEREREREREREJlAJUIiIiIiIiIiISKAWoREREREREREQkUApQiYiIiIiIiIhIoBSgEhEREZF68B7gFPC4KKgGFmBb4GVgJdbW+RV4jbWBOHBTBfZdjOHYexwacDtERKRKKEAlIiIiIvXiB0CD+zgjw7LTAmpXoe4BPgPWBIYA31bgNY7ArgHGAl0rsH8REZGSKEAlIiIiIhK8rsAmwHRgBfAusHEFXmccMA3oDRxYgf2LiIiIiIiIiNQVBxhT4nNPd5+/foZ1J5Ec8ncSNtxtsfv9/e42/YArgLnAauA/wP459jMeuBZYimVBnZe27YbAY8BCYDnwKLBjhv14j6dSnjsSeMNtxwLgZqBXEe/FMwyYCWzgrn8mw88mX1sLWZ+vzZdmeL8fZWlLJmPc54iIiIiIiIiIVFylAlQAPd31HwKjgR5YgMcL6lztPr7nbnsoFmzZPMt+ZgL7AU3Aye6yXVO2+4+77/5Y9tLVWKZU+n6OSdv//kACuADoA2yFZVc9hw1bLOS9eG4jOfTxFawW1TrpP5gC2ppvfSFtLqcGlQJUIiIiIiIiItJhOiJAdVsR+/wzlgmUaT9/SlkWxgqd/9b9vivt30sEyyxK3096gOp9YFbaspHutnukPTfXe+kOLMGKpAOc6D7n3LTt8rW1kPdSSJsVoBIRkTZUg0pE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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Start Time : 2571.73599414\n", + "End Time : 2571.73943057\n", + "Execution Time : 0.003\n", + "Execution Time (ms): 3.436\n" + ] + } + ], + "source": [ + "#Another way to discover the tree\n", + "# # create the process tree\n", + "#tree = inductive_miner.apply_tree(log)\n", + "\n", + "# viz\n", + "#gviz = pt_visualizer.apply(tree)\n", + "#pt_visualizer.view(gviz)# convert the process tree to a petri net\n", + "\n", + "start_time = time.perf_counter()\n", + "process_tree = pm4py.discover_process_tree_inductive(log)\n", + "print(process_tree)\n", + "end_time = time.perf_counter()\n", + "pm4py.view_process_tree(process_tree)\n", + "\n", + "start_time_2 = time.perf_counter()\n", + "bpmn_model = pm4py.convert_to_bpmn(process_tree)\n", + "end_time_2 = time.perf_counter()\n", + "print(f\"Execution Time : {end_time_2 - start_time_2:0.3f}\" )\n", + "timeDiff_2 = end_time_2 - start_time_2\n", + "print(f\"Execution Time (ms): {timeDiff_2*1000:0.3f}\" )\n", + "\n", + "pm4py.view_bpmn(bpmn_model)\n", + "\n", + "print(f\"Start Time : {start_time}\")\n", + "print(f\"End Time : {end_time}\")\n", + "timeDiff_3 = end_time - start_time\n", + "print(f\"Execution Time : {timeDiff_3:0.3f}\")\n", + "print(f\"Execution Time (ms): {timeDiff_3*1000:0.3f}\" )\n", + "\n", + "\n", + "#net, initial_marking, final_marking = pt_converter.apply(tree)\n", + "\n", + "# alternatively, use the inductive_miner to create a petri net from scratch\n", + "# net, initial_marking, final_marking = inductive_miner.apply(log)\n", + "\n", + "# viz\n", + "parameters = {pn_visualizer.Variants.FREQUENCY.value.Parameters.FORMAT: \"png\"}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/rafaelapb/.local/lib/python3.8/site-packages/pm4py/algo/discovery/dfg/adapters/pandas/df_statistics.py:82: FutureWarning: Passing a set as an indexer is deprecated and will raise in a future version. Use a list instead.\n", + " df_reduced = df[{case_id_glue, activity_key, target_activity_key}]\n" + ] + }, + { + "data": { + "image/png": "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rQHM+++/XzitVatWpm3btj7LrVmzxgBm7NixPsu1a9fOZ7nDhw+bBg0alLr+o0ePml69epnJkyeXWktOTo5JSEgw6enpxhhjXnnlFQOYp556yme5Y8eOGcDMnj27cFpeXp5JTk72a74xxlx00UUlPuP8+fMNYNLS0owxxixevNgAZt26daXWW9To0aNN3bp1y12mX79+pnnz5uUuc/3115tOnTpVuL3ibrnllhozKk9I74HS09PZvHkzV199tc/0Sy+9lHr16rFkyRKf5bp16+azXHx8PAcOHCix3pycHPr06UP9+vUZOnRoqdt+88036dixI02bNgVgwIABREdHlxgwIzY2lssvv5xHHnmEuXPncuzYMWrVqsW+ffv8mp+ens6WLVu45pprfNbbuXNnANLS0vz4Tfnau3cvCQnu4zkmJCSwd+9e5/VUZyEdoIyMDAAaNGhQYl5SUlLh/PKWK83IkSOJiYlh3rx5ZR4jTZ06lcGDB/tsr1evXmzdupXly5f7LLto0SJuvPFGHnjgARISEujTp4/PesubX1D7xIkTfY7HCnrOduzY4ddnKiozM5Po6OhKv6+46OhoDh486Lye6iykA9SwYUOAUv8RDxw4UDi/vOVK89hjj/Huu+/SoUMHbr/9do4ePeoz//vvv2fp0qUMGTLE50s9f/58oOSwTYmJiYwfP55du3axbNkycnNz6datG9u2batwfkHtY8aMwRhT4jVjxgw/f1u+9eTl5VX6fcXl5eX5/UeppgrpADVr1oxWrVqV6An6+uuvyc7OLjwQL1hu2bJlPsvt3r2b2NjYEs24Cy64gOjoaGbMmMGOHTsYM2aMz/zU1FQGDhxY4st85MgRGjZsyJw5c8jJyQFsc6lduzPPDu7SpQuTJ0/mxIkTrF69usL5zZo1o3Xr1qxaVfIZXB06dCg8iI+M9P+fqnHjxmRlZfm9fFmysrJo3FhPZvFKlXQizJ8/30RGRpo//OEPJiMjw2zatMlcccUV5sILLzSHDh0qsdxjjz1mMjIyzPbt201KSoq55557yl3/iy++aCIiIsyHH35ojDEmPz/ftGzZ0mzcuLHUGv/6178awKSmphpjjNmzZ48BzAsvvGAOHTpkMjMzzZgxY0xsbKzZtm1bhfONMea9994zUVFR5plnnjH79+83+/fvNw8++KC55JJLzLFjx4wxxmzc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nw3zugQJAt2RKuJAARJxoACJOFCARBwoQCIOFCARBwqQiAMFSMSBAiTiQAEScaAASaiLAeK9LqIsuhZOQl177G3xmcBOYAewHdgIbADWAwe9Kk4BklC3EbgfOB/40en/dsKOs5dwepk9wJfAp6dfq7FDiQWcc4Aef7w1e/durnC5Pn0e5cYbn3LdXEB8//2XvP76b9i582tOnDhKQkJTnnsuvUq3kZW1izFjfkS3bvcwePCkKl13ZXz77RJeeKEnTzyxjqZN2/r1nqNHs/jXv37NgAEvULduMvv2beGjjyawdu18Dh3aS4MGP6JDh75cf/3j1KmTUPi+qVPvJCXldzRp0sal5BzgvTLmNQMuAdoBV2AH+R8L5AKfAPOAt7B3BvujJTADaIK9sbRCVXIMNHbsf5k82TB5suGWW54rMW3AgBerYjMB889/DiIxsSkvvLCPP/95I+ecU6fKt7FixXSMyWfVqjfIy8ut8vUHyrFj2Ywb143WrbtTt64d2vvvf7+J77//ipEjF/DSSwcZPHgSK1fO4vnnu5Off7LwvT16jOSll3qzc+faQJWXDnwIjAcGAE2BC4F7sc2+l7B7pw+wg72UZwiwiEqOX17jOxHy8nLZu3czF198HTEx8Zx33sX89a9bqnw7K1akctll/Tl6NIs1a96q8vUHyptvjiExsSldu97tM33QoL/TtGlbzjmnDhdffC3XX/84O3d+zbp17xcu07x5J7p1G84//zmI/Pyg3eC8DZgG3AKcCwzCjk+xEPgK6FfKe+Kwg790xh5T+c25CfeXv2yqcJnrrnsAgI8+msjrr48E4Je/nMDu3RtYtWo2R49mcvnlA7jnnjc4ciSDDz54lq++eovMzHTOO681N9zwJJdeeuZzF13P4MGT2L17I59/Po2YmHi6dRtO375/LFx2//5tzJ49mm3bPiMv7zgXX3wtvXv/ngsu6OKznhkzhjNjxnDatEnhgQfsOPDffLOAd999gl271lO7dn06dvwFN988jtq16/n1WQps3foZ0dGx3HTTWFavnstnn03liit+WeL3VF6t/syvqOY33/w9H3zwLABPPNEOgKSk5owdu73Uf7fc3MOsWDGN++5702f6k0+W/I41a9YegAMHvveZ3r37CN59909s3ryUiy8O+gBFOcCc069LgUewTbppwEjsIJgFyw06mw0EdQ/U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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'createAsset': (node:createAsset connections:{lockAsset:[0.5]}), 'lockAsset': (node:lockAsset connections:{MintAsset:[0.5]}), 'MintAsset': (node:MintAsset connections:{TransferAsset:[0.5]}), 'TransferAsset': (node:TransferAsset connections:{TransferAsset:[0.5], BurnAsset:[0.5]}), 'BurnAsset': (node:BurnAsset connections:{})}\n" + ] + }, + { + "data": { + "image/png": "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WpT4XLrOk23bYO5cGDvWPh8/3j5On57atrIS7rgDDjsMSkqgc2e7ejlnDsRiubcD2LwZfvADOOggKCqCbt1g9GhYurRh81PbsmX2j8+gQfW3zdYxx9jHl19uunFKgzRuNbh61aq83BAIGJYvT7T52c8Ml15q//2Pf2ReDb7wQvve3r3pX7/wQsNrrxl27TK88oqhbVvDCWnU2i4AACAASURBVCc0bLXxoYcMp52WeL55syEctquNmzYlt50wwVBWZli40M7jxo2Gm26yNb36au7tNmywq9/l5Yb5823n2vvvG045xVBcbOexsavBM2fa6d53X/1ts10N3rHDjnP4cK0G+6zxYTXGcP/99j/0yivt89277Y9y2bLGh3Xu3HQ/Ahu0XH/cxx5rmDEj+bVRo+z4pkxJfr1fP8NJJ6WOY8CA5BBm227sWDudWbOS2332maFNG8NxxzU+rA88YKfx8MNNF1ZjDJ5ne+0V1nrl72pwte9+166izp4NH30Ejzxi92EOHNj4cZ9wQvLz6p7lDRtyG89779n9qxfVuph+9apw7V7hc86B116Db30LXn89sUq7YgWcemru7V58EQIBuxpf0wEHwJFH2h7adetym6faqjclmvpaUaEQ7N3btONspfI/rO3awQ032B/qf/6n3e66/famGXdZrfs/FRXZx1x390yfDjt3Qmlp8jbxBRfY9z/4AN58M9H+4YftLp7Vq+GMM6BDBxvMF15IHm827SorYccOW3NZWeqBJUuW2HarVuU2T7UVF9vHSKRx46ktGm1Y51QByv+wAnz/+/aH+NRTtoPj+ONbdvqel/m9SARmzbIdNibNNW5vuMG2q7l09Ty46ir4859h+3a7ZDTGdgg9+GBu7dq0sR1koVDd1x0+7bTs5ieTHj3s444duX82k4oKW1v1uKVOboS1rAwmTrSPTbVUzUVJCVRVJZ4feig8+qj999y50LWrPSAgnWuusY+zZydW9zp2hA8/tP8Oh+GssxI91TV3Y2TbbvRou4RatCh1+vffD3362PezmZ9MjjrKPjZ2dbqm9euTxy11ciOsYHdhbN+eORTN6dhjYeVKWLvWHq+8ejUMH27fe/xxuPrqzJ896ig48US7RHr++cTr3/623datrITPP4cHHrBLmdNPT/58Nu1++lO7v/nqq+1RXzt22F1JjzwCd91lNx1q7p6p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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dfg, start_activities, end_activities = pm4py.discover_dfg(log)\n", + "pm4py.view_dfg(dfg, start_activities, end_activities)\n", + "\n", + "map = pm4py.discover_heuristics_net(log)\n", + "print(map)\n", + "pm4py.view_heuristics_net(map)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/rafaelapb/.local/lib/python3.8/site-packages/pm4py/algo/discovery/dfg/adapters/pandas/df_statistics.py:82: FutureWarning: Passing a set as an indexer is deprecated and will raise in a future version. Use a list instead.\n", + " df_reduced = df[{case_id_glue, activity_key, target_activity_key}]\n", + "/home/rafaelapb/.local/lib/python3.8/site-packages/pm4py/algo/discovery/dfg/adapters/pandas/df_statistics.py:82: FutureWarning: Passing a set as an indexer is deprecated and will raise in a future version. Use a list instead.\n", + " df_reduced = df[{case_id_glue, activity_key, target_activity_key}]\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# heuristics miner\n", + "net, im, fm = heuristics_miner.apply(log)\n", + "\n", + "# viz\n", + "gviz = pn_visualizer.apply(net, im, fm)\n", + "pn_visualizer.view(gviz)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.8 ('base')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + }, + "vscode": { + "interpreter": { + "hash": "994fd3bf715f7d00910c6929cedf6117267fec036ef9d2716f71f2b8e3dc9b3e" + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process_conformance.ipynb b/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process_conformance.ipynb new file mode 100644 index 0000000000..fb7e30c891 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process_conformance.ipynb @@ -0,0 +1,147 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [ + { + "ename": "ImportError", + "evalue": "dlopen(/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/cvxopt/base.cpython-310-darwin.so, 0x0002): tried: '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/cvxopt/base.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have (x86_64), need (arm64e)))", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/rafaelapb/Projects/cactus-with-branches/packages/cactus-plugin-cc-tx-visualization/src/test/fixtures/python/process_conformance.ipynb Cell 1\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mos\u001b[39;00m\u001b[39m# uncomment if problems with dependencies\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[39m#%pip install pm4py\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[39m#%pip install pandas\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mdatetime\u001b[39;00m \u001b[39mas\u001b[39;00m \u001b[39mdt\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mtime\u001b[39;00m\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pm4py/__init__.py:20\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39msys\u001b[39;00m\n\u001b[1;32m 18\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mtime\u001b[39;00m\n\u001b[0;32m---> 20\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m \u001b[39mimport\u001b[39;00m util, objects, statistics, algo, visualization\n\u001b[1;32m 21\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39manalysis\u001b[39;00m \u001b[39mimport\u001b[39;00m check_soundness, solve_marking_equation, solve_extended_marking_equation, \\\n\u001b[1;32m 22\u001b[0m construct_synchronous_product_net, insert_artificial_start_end\n\u001b[1;32m 23\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mconformance\u001b[39;00m \u001b[39mimport\u001b[39;00m conformance_diagnostics_token_based_replay, conformance_diagnostics_alignments, \\\n\u001b[1;32m 24\u001b[0m fitness_token_based_replay, \\\n\u001b[1;32m 25\u001b[0m fitness_alignments, precision_token_based_replay, \\\n\u001b[1;32m 26\u001b[0m precision_alignments, conformance_alignments, conformance_tbr, evaluate_precision_alignments, \\\n\u001b[1;32m 27\u001b[0m evaluate_precision_tbr, evaluate_fitness_tbr, evaluate_fitness_alignments, conformance_diagnostics_footprints, \\\n\u001b[1;32m 28\u001b[0m fitness_footprints, precision_footprints, check_is_fitting\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pm4py/util/__init__.py:18\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[39m This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[39m along with PM4Py. If not, see .\u001b[39;00m\n\u001b[1;32m 16\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[0;32m---> 18\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mutil\u001b[39;00m \u001b[39mimport\u001b[39;00m variants_util, lp, constants, points_subset, business_hours, regex, xes_constants, vis_utils, \\\n\u001b[1;32m 19\u001b[0m dt_parsing, colors, exec_utils, pandas_utils, typing\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pm4py/util/lp/__init__.py:17\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[39m This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[39m along with PM4Py. If not, see .\u001b[39;00m\n\u001b[1;32m 16\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[0;32m---> 17\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mutil\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mlp\u001b[39;00m \u001b[39mimport\u001b[39;00m solver, util, variants\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pm4py/util/lp/solver.py:58\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 55\u001b[0m DEFAULT_LP_SOLVER_VARIANT \u001b[39m=\u001b[39m SCIPY\n\u001b[1;32m 57\u001b[0m \u001b[39mif\u001b[39;00m pkgutil\u001b[39m.\u001b[39mfind_loader(\u001b[39m\"\u001b[39m\u001b[39mcvxopt\u001b[39m\u001b[39m\"\u001b[39m):\n\u001b[0;32m---> 58\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mpm4py\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mutil\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mlp\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mvariants\u001b[39;00m \u001b[39mimport\u001b[39;00m cvxopt_solver, cvxopt_solver_custom_align, cvxopt_solver_custom_align_ilp, \\\n\u001b[1;32m 59\u001b[0m cvxopt_solver_custom_align_arm\n\u001b[1;32m 61\u001b[0m custom_solver \u001b[39m=\u001b[39m cvxopt_solver_custom_align\n\u001b[1;32m 62\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[1;32m 63\u001b[0m \u001b[39m# for ARM-based Linux, we need to use a different call to GLPK\u001b[39;00m\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/pm4py/util/lp/variants/cvxopt_solver.py:19\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[39m This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[39m along with PM4Py. If not, see .\u001b[39;00m\n\u001b[1;32m 16\u001b[0m \u001b[39m'''\u001b[39;00m\n\u001b[1;32m 17\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39msys\u001b[39;00m\n\u001b[0;32m---> 19\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mcvxopt\u001b[39;00m \u001b[39mimport\u001b[39;00m matrix, solvers\n\u001b[1;32m 22\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mapply\u001b[39m(c, Aub, bub, Aeq, beq, parameters\u001b[39m=\u001b[39m\u001b[39mNone\u001b[39;00m):\n\u001b[1;32m 23\u001b[0m \u001b[39m\"\"\"\u001b[39;00m\n\u001b[1;32m 24\u001b[0m \u001b[39m Gets the overall solution of the problem\u001b[39;00m\n\u001b[1;32m 25\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[39m Solution of the LP problem by the given algorithm\u001b[39;00m\n\u001b[1;32m 45\u001b[0m \u001b[39m \"\"\"\u001b[39;00m\n", + "File \u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/cvxopt/__init__.py:50\u001b[0m, in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 33\u001b[0m __copyright__ \u001b[39m=\u001b[39m \u001b[39m\"\"\"\u001b[39m\u001b[39mCopyright (c) 2012-2022 M. Andersen and L. Vandenberghe.\u001b[39m\n\u001b[1;32m 34\u001b[0m \u001b[39mCopyright (c) 2010-2011 L. Vandenberghe.\u001b[39m\n\u001b[1;32m 35\u001b[0m \u001b[39mCopyright (c) 2004-2009 J. Dahl and L. Vandenberghe.\u001b[39m\u001b[39m\"\"\"\u001b[39m\n\u001b[1;32m 37\u001b[0m __license__ \u001b[39m=\u001b[39m \u001b[39m\"\"\"\u001b[39m\u001b[39mThis program is free software; you can redistribute it and/or modify\u001b[39m\n\u001b[1;32m 38\u001b[0m \u001b[39mit under the terms of the GNU General Public License as published by\u001b[39m\n\u001b[1;32m 39\u001b[0m \u001b[39mthe Free Software Foundation; either version 3 of the License, or\u001b[39m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 47\u001b[0m \u001b[39mYou should have received a copy of the GNU General Public License\u001b[39m\n\u001b[1;32m 48\u001b[0m \u001b[39malong with this program. If not, see .\u001b[39m\u001b[39m\"\"\"\u001b[39m\n\u001b[0;32m---> 50\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39mcvxopt\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mbase\u001b[39;00m\n\u001b[1;32m 52\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mcopyright\u001b[39m():\n\u001b[1;32m 53\u001b[0m \u001b[39mprint\u001b[39m(__copyright__)\n", + "\u001b[0;31mImportError\u001b[0m: dlopen(/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/cvxopt/base.cpython-310-darwin.so, 0x0002): tried: '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/cvxopt/base.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have (x86_64), need (arm64e)))" + ] + } + ], + "source": [ + "import os# uncomment if problems with dependencies\n", + "#%pip install pm4py\n", + "#%pip install pandas\n", + "import pm4py\n", + "import datetime as dt\n", + "import time\n", + "import pandas\n", + "path = os.getcwd()\n", + "parent = os.path.dirname(path)\n", + "\n", + "# Change path if necessary \n", + "file_path = parent + \"/csv/use-case-besu-fabric-6-events.csv\"\n", + "file_path_other_model = parent + \"/csv/dummy-use-case-invalid.csv\"\n", + "\n", + "import sys\n", + "\n", + "if __name__ == '__main__':\n", + " print(sys.argv)\n", + " print(file_path)\n", + "\n", + "\n", + "\n", + "# import sys\n", + "\n", + "# accept command line arguments\n", + "# inputArg1 = sys.argv[1]\n", + "\n", + "#print('inputArg1: ',inputArg1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "\n", + "\n", + "def import_csv_original(file_path):\n", + " event_log = pandas.read_csv(file_path, sep=';')\n", + " event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp')\n", + " return event_log\n", + "\n", + "def getStartActivities(event_log):\n", + " s = pm4py.get_start_activities(event_log)\n", + " print(\"Start activities: {}\\n\".format(s))\n", + " return s\n", + "def getEndActivities(event_log):\n", + " e = pm4py.get_end_activities(event_log)\n", + " print(\"End activities: {}\\n\".format(e))\n", + " return (e)\n", + "\n", + "def getAttributeFromLog(event_log, attr):\n", + " entries = pm4py.get_event_attribute_values(event_log,attr)\n", + " print(\"Entries: {}\\n\".format(entries))\n", + " return entries\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'alignment': [('createAsset', 'createAsset'), ('MintAsset', '>>'), ('lockAsset', 'lockAsset'), ('>>', 'MintAsset'), ('transferAsset', 'transferAsset'), ('>>', None), ('transferAsset', 'transferAsset'), ('BurnAsset', 'BurnAsset')], 'cost': 20001, 'visited_states': 10, 'queued_states': 26, 'traversed_arcs': 28, 'lp_solved': 8, 'fitness': 0.8181818181818181, 'bwc': 110000}]\n" + ] + } + ], + "source": [ + "\n", + "log = import_csv_original(file_path)\n", + "log_other_model = import_csv_original(file_path_other_model)\n", + "\n", + "net, initial_marking, final_marking = pm4py.discover_petri_net_inductive(log)\n", + "\n", + "aligned_traces = pm4py.conformance_diagnostics_alignments(log_other_model, net, initial_marking, final_marking)\n", + "\n", + "print(aligned_traces)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.2 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + }, + "vscode": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/solidity/LockAsset.json b/packages/cactus-plugin-cc-tx-visualization/src/test/solidity/LockAsset.json new file mode 100644 index 0000000000..50be30cea5 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/solidity/LockAsset.json @@ -0,0 +1,2007 @@ +{ + "contractName": "LockAsset", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + }, + { + "internalType": "uint256", + "name": "size", + "type": "uint256" + } + ], + "name": "createAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "deleteAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "getAsset", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "bool", + "name": "isLock", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "size", + "type": "uint256" + } + ], + "internalType": "struct Asset", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "lockAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "unLockAsset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.8.7+commit.e28d00a7\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"name\":\"createAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"deleteAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"getAsset\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isLock\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"size\",\"type\":\"uint256\"}],\"internalType\":\"struct Asset\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"lockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"unLockAsset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol\":\"LockAsset\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol\":{\"keccak256\":\"0x878fc27f22785593c1b35005ecf333e1f93e6dcf932b3d6e2b24d6be790b996a\",\"urls\":[\"bzz-raw://d8be1567f5e11fb718d8849f4dc9b8a8e467135ecd04c5f796c938b7363daaf2\",\"dweb:/ipfs/QmQuteuiTygZxjDnLiq3GNgJNFmPSRqbE2Q9vm85WgVbox\"]}},\"version\":1}", + "bytecode": "608060405234801561001057600080fd5b50610475806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80635e82d0a61461005c578063cd5286d014610071578063db9cc410146100b5578063def60e0d146100c8578063e24aa37c146100db575b600080fd5b61006f61006a3660046103a1565b6100ee565b005b61008461007f3660046103a1565b610164565b6040805182516001600160a01b03168152602080840151151590820152918101519082015260600160405180910390f35b61006f6100c33660046103e3565b6101dc565b61006f6100d63660046103a1565b610266565b61006f6100e93660046103a1565b6102ad565b6000806000848460405161010392919061042f565b9081526020016040518091039020600101541190508061012257600080fd5b60016000848460405161013692919061042f565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60408051606081018252600080825260208201819052918101919091526000838360405161019392919061042f565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116101e957600080fd5b80600084846040516101fc92919061042f565b908152602001604051809103902060010181905550336000848460405161022492919061042f565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b0319909316929092179091556000908190610136908690869061042f565b6000806000848460405161027b92919061042f565b9081526020016040518091039020600101541190508061029a57600080fd5b600080848460405161013692919061042f565b600080600084846040516102c292919061042f565b908152602001604051809103902060010154119050806102e157600080fd5b60008084846040516102f492919061042f565b9081526040519081900360200190205460ff600160a01b9091041690508061031b57600080fd5b6000848460405161032d92919061042f565b90815260405190819003602001902080546001600160a81b0319168155600060019091015550505050565b60008083601f84011261036a57600080fd5b50813567ffffffffffffffff81111561038257600080fd5b60208301915083602082850101111561039a57600080fd5b9250929050565b600080602083850312156103b457600080fd5b823567ffffffffffffffff8111156103cb57600080fd5b6103d785828601610358565b90969095509350505050565b6000806000604084860312156103f857600080fd5b833567ffffffffffffffff81111561040f57600080fd5b61041b86828701610358565b909790965060209590950135949350505050565b818382376000910190815291905056fea26469706673582212203e656ee2af105d66466451b5ca09a2a5780b62c439dd43befb63a10687d2423b64736f6c63430008070033", + "deployedBytecode": "608060405234801561001057600080fd5b50600436106100575760003560e01c80635e82d0a61461005c578063cd5286d014610071578063db9cc410146100b5578063def60e0d146100c8578063e24aa37c146100db575b600080fd5b61006f61006a3660046103a1565b6100ee565b005b61008461007f3660046103a1565b610164565b6040805182516001600160a01b03168152602080840151151590820152918101519082015260600160405180910390f35b61006f6100c33660046103e3565b6101dc565b61006f6100d63660046103a1565b610266565b61006f6100e93660046103a1565b6102ad565b6000806000848460405161010392919061042f565b9081526020016040518091039020600101541190508061012257600080fd5b60016000848460405161013692919061042f565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60408051606081018252600080825260208201819052918101919091526000838360405161019392919061042f565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116101e957600080fd5b80600084846040516101fc92919061042f565b908152602001604051809103902060010181905550336000848460405161022492919061042f565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b0319909316929092179091556000908190610136908690869061042f565b6000806000848460405161027b92919061042f565b9081526020016040518091039020600101541190508061029a57600080fd5b600080848460405161013692919061042f565b600080600084846040516102c292919061042f565b908152602001604051809103902060010154119050806102e157600080fd5b60008084846040516102f492919061042f565b9081526040519081900360200190205460ff600160a01b9091041690508061031b57600080fd5b6000848460405161032d92919061042f565b90815260405190819003602001902080546001600160a81b0319168155600060019091015550505050565b60008083601f84011261036a57600080fd5b50813567ffffffffffffffff81111561038257600080fd5b60208301915083602082850101111561039a57600080fd5b9250929050565b600080602083850312156103b457600080fd5b823567ffffffffffffffff8111156103cb57600080fd5b6103d785828601610358565b90969095509350505050565b6000806000604084860312156103f857600080fd5b833567ffffffffffffffff81111561040f57600080fd5b61041b86828701610358565b909790965060209590950135949350505050565b818382376000910190815291905056fea26469706673582212203e656ee2af105d66466451b5ca09a2a5780b62c439dd43befb63a10687d2423b64736f6c63430008070033", + "sourceMap": "543:1053:0:-:0;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "543:1053:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;946:154;;;;;;:::i;:::-;;:::i;:::-;;798:105;;;;;;:::i;:::-;;:::i;:::-;;;;1753:13:1;;-1:-1:-1;;;;;1749:39:1;1731:58;;1859:4;1847:17;;;1841:24;1834:32;1827:40;1805:20;;;1798:70;1912:17;;;1906:24;1884:20;;;1877:54;1719:2;1704:18;798:105:0;;;;;;;607:188;;;;;;:::i;:::-;;:::i;1144:157::-;;;;;;:::i;:::-;;:::i;1304:289::-;;;;;;:::i;:::-;;:::i;946:154::-;999:16;1034:1;1018:6;1025:2;;1018:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;999:36;;1051:11;1043:20;;;;;;1091:4;1071:6;1078:2;;1071:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;;;-1:-1:-1;;;1071:24:0;-1:-1:-1;;;;1071:24:0;;;;;;;;;-1:-1:-1;;;946:154:0:o;798:105::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;888:6:0;895:2;;888:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;881:17;;;;;;;-1:-1:-1;;;;;881:17:0;;;;-1:-1:-1;;;881:17:0;;;;;;;;;;;;;;;;;;;;;;888:10;798:105;-1:-1:-1;;;798:105:0:o;607:188::-;687:1;682:4;:6;674:15;;;;;;714:4;697:6;704:2;;697:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;:21;;;;747:10;726:6;733:2;;726:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:31;;-1:-1:-1;;;;;726:31:0;;;;-1:-1:-1;;;;;;726:31:0;;;;;;;;;;:18;;;;765:10;;772:2;;;;765:10;:::i;1144:157::-;1199:16;1234:1;1218:6;1225:2;;1218:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;1199:36;;1251:11;1243:20;;;;;;1291:5;1271:6;1278:2;;1271:10;;;;;;;:::i;1304:289::-;1360:16;1395:1;1379:6;1386:2;;1379:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;;:17;1360:36;;1412:11;1404:20;;;;;;1495:18;1516:6;1523:2;;1516:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:17;;-1:-1:-1;;;1516:17:0;;;;;-1:-1:-1;1516:17:0;1541:22;;;;;;1578:6;1585:2;;1578:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;1571:17;;-1:-1:-1;;;;;;1571:17:0;;;1578:10;1571:17;;;;;-1:-1:-1;;;;1304:289:0:o;14:348:1:-;66:8;76:6;130:3;123:4;115:6;111:17;107:27;97:55;;148:1;145;138:12;97:55;-1:-1:-1;171:20:1;;214:18;203:30;;200:50;;;246:1;243;236:12;200:50;283:4;275:6;271:17;259:29;;335:3;328:4;319:6;311;307:19;303:30;300:39;297:59;;;352:1;349;342:12;297:59;14:348;;;;;:::o;367:411::-;438:6;446;499:2;487:9;478:7;474:23;470:32;467:52;;;515:1;512;505:12;467:52;555:9;542:23;588:18;580:6;577:30;574:50;;;620:1;617;610:12;574:50;659:59;710:7;701:6;690:9;686:22;659:59;:::i;:::-;737:8;;633:85;;-1:-1:-1;367:411:1;-1:-1:-1;;;;367:411:1:o;783:479::-;863:6;871;879;932:2;920:9;911:7;907:23;903:32;900:52;;;948:1;945;938:12;900:52;988:9;975:23;1021:18;1013:6;1010:30;1007:50;;;1053:1;1050;1043:12;1007:50;1092:59;1143:7;1134:6;1123:9;1119:22;1092:59;:::i;:::-;1170:8;;1066:85;;-1:-1:-1;1252:2:1;1237:18;;;;1224:32;;783:479;-1:-1:-1;;;;783:479:1:o;1267:273::-;1452:6;1444;1439:3;1426:33;1408:3;1478:16;;1503:13;;;1478:16;1267:273;-1:-1:-1;1267:273:1:o", + "sourcePath": "/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol", + "compiler": { + "name": "solc", + "version": "0.8.7+commit.e28d00a7" + }, + "ast": { + "absolutePath": "/Users/jasonwang/cactus-odap-merge/packages/cactus-plugin-ledger-connector-besu/src/test/solidity/hello-world-contract/lock-asset.sol", + "exportedSymbols": { + "Asset": [ + 8 + ], + "LockAsset": [ + 150 + ] + }, + "id": 151, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.7", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "413:24:0" + }, + { + "canonicalName": "Asset", + "id": 8, + "members": [ + { + "constant": false, + "id": 3, + "mutability": "mutable", + "name": "creator", + "nameLocation": "464:7:0", + "nodeType": "VariableDeclaration", + "scope": 8, + "src": "456:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "456:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 5, + "mutability": "mutable", + "name": "isLock", + "nameLocation": "482:6:0", + "nodeType": "VariableDeclaration", + "scope": 8, + "src": "477:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 4, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "477:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 7, + "mutability": "mutable", + "name": "size", + "nameLocation": "499:4:0", + "nodeType": "VariableDeclaration", + "scope": 8, + "src": "494:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 6, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "494:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "name": "Asset", + "nameLocation": "445:5:0", + "nodeType": "StructDefinition", + "scope": 151, + "src": "438:68:0", + "visibility": "public" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 150, + "linearizedBaseContracts": [ + 150 + ], + "name": "LockAsset", + "nameLocation": "552:9:0", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 13, + "mutability": "mutable", + "name": "assets", + "nameLocation": "597:6:0", + "nodeType": "VariableDeclaration", + "scope": 150, + "src": "571:32:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string => struct Asset)" + }, + "typeName": { + "id": 12, + "keyType": { + "id": 9, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "580:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "nodeType": "Mapping", + "src": "571:25:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string => struct Asset)" + }, + "valueType": { + "id": 11, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 10, + "name": "Asset", + "nodeType": "IdentifierPath", + "referencedDeclaration": 8, + "src": "590:5:0" + }, + "referencedDeclaration": 8, + "src": "590:5:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", + "typeString": "struct Asset" + } + } + }, + "visibility": "internal" + }, + { + "body": { + "id": 48, + "nodeType": "Block", + "src": "666:129:0", + "statements": [ + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 23, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 21, + "name": "size", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "682:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 22, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "687:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "682:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 20, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "674:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 24, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "674:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 25, + "nodeType": "ExpressionStatement", + "src": "674:15:0" + }, + { + "expression": { + "id": 31, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 26, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "697:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 28, + "indexExpression": { + "id": 27, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 15, + "src": "704:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "697:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 29, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "size", + "nodeType": "MemberAccess", + "referencedDeclaration": 7, + "src": "697:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 30, + "name": "size", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "714:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "697:21:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 32, + "nodeType": "ExpressionStatement", + "src": "697:21:0" + }, + { + "expression": { + "id": 39, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 33, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "726:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 35, + "indexExpression": { + "id": 34, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 15, + "src": "733:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "726:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 36, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "creator", + "nodeType": "MemberAccess", + "referencedDeclaration": 3, + "src": "726:18:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 37, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4294967281, + "src": "747:3:0", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 38, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "src": "747:10:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "726:31:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 40, + "nodeType": "ExpressionStatement", + "src": "726:31:0" + }, + { + "expression": { + "id": 46, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 41, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "765:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 43, + "indexExpression": { + "id": 42, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 15, + "src": "772:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "765:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 44, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "765:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 45, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "785:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "765:25:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 47, + "nodeType": "ExpressionStatement", + "src": "765:25:0" + } + ] + }, + "functionSelector": "db9cc410", + "id": 49, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "createAsset", + "nameLocation": "616:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 18, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "id", + "nameLocation": "645:2:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "629:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "629:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 17, + "mutability": "mutable", + "name": "size", + "nameLocation": "654:4:0", + "nodeType": "VariableDeclaration", + "scope": 49, + "src": "649:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 16, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "649:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "627:32:0" + }, + "returnParameters": { + "id": 19, + "nodeType": "ParameterList", + "parameters": [], + "src": "666:0:0" + }, + "scope": 150, + "src": "607:188:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 61, + "nodeType": "Block", + "src": "873:30:0", + "statements": [ + { + "expression": { + "baseExpression": { + "id": 57, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "888:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 59, + "indexExpression": { + "id": 58, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 51, + "src": "895:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "888:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "functionReturnParameters": 56, + "id": 60, + "nodeType": "Return", + "src": "881:17:0" + } + ] + }, + "functionSelector": "cd5286d0", + "id": 62, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getAsset", + "nameLocation": "807:8:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 52, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 51, + "mutability": "mutable", + "name": "id", + "nameLocation": "832:2:0", + "nodeType": "VariableDeclaration", + "scope": 62, + "src": "816:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 50, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "816:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "815:20:0" + }, + "returnParameters": { + "id": 56, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 55, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 62, + "src": "857:12:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_memory_ptr", + "typeString": "struct Asset" + }, + "typeName": { + "id": 54, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 53, + "name": "Asset", + "nodeType": "IdentifierPath", + "referencedDeclaration": 8, + "src": "857:5:0" + }, + "referencedDeclaration": 8, + "src": "857:5:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", + "typeString": "struct Asset" + } + }, + "visibility": "internal" + } + ], + "src": "856:14:0" + }, + "scope": 150, + "src": "798:105:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 87, + "nodeType": "Block", + "src": "991:109:0", + "statements": [ + { + "assignments": [ + 68 + ], + "declarations": [ + { + "constant": false, + "id": 68, + "mutability": "mutable", + "name": "assetExsist", + "nameLocation": "1004:11:0", + "nodeType": "VariableDeclaration", + "scope": 87, + "src": "999:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 67, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "999:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 75, + "initialValue": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 74, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "baseExpression": { + "id": 69, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1018:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 71, + "indexExpression": { + "id": 70, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 64, + "src": "1025:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1018:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 72, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "size", + "nodeType": "MemberAccess", + "referencedDeclaration": 7, + "src": "1018:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 73, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1034:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "1018:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "999:36:0" + }, + { + "expression": { + "arguments": [ + { + "id": 77, + "name": "assetExsist", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 68, + "src": "1051:11:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 76, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1043:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 78, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1043:20:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 79, + "nodeType": "ExpressionStatement", + "src": "1043:20:0" + }, + { + "expression": { + "id": 85, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 80, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1071:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 82, + "indexExpression": { + "id": 81, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 64, + "src": "1078:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1071:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 83, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1071:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "74727565", + "id": 84, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1091:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "1071:24:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 86, + "nodeType": "ExpressionStatement", + "src": "1071:24:0" + } + ] + }, + "functionSelector": "5e82d0a6", + "id": 88, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "lockAsset", + "nameLocation": "955:9:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 65, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 64, + "mutability": "mutable", + "name": "id", + "nameLocation": "981:2:0", + "nodeType": "VariableDeclaration", + "scope": 88, + "src": "965:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 63, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "965:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "964:20:0" + }, + "returnParameters": { + "id": 66, + "nodeType": "ParameterList", + "parameters": [], + "src": "991:0:0" + }, + "scope": 150, + "src": "946:154:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 113, + "nodeType": "Block", + "src": "1191:110:0", + "statements": [ + { + "assignments": [ + 94 + ], + "declarations": [ + { + "constant": false, + "id": 94, + "mutability": "mutable", + "name": "assetExsist", + "nameLocation": "1204:11:0", + "nodeType": "VariableDeclaration", + "scope": 113, + "src": "1199:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 93, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1199:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 101, + "initialValue": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 100, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "baseExpression": { + "id": 95, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1218:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 97, + "indexExpression": { + "id": 96, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 90, + "src": "1225:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1218:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 98, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "size", + "nodeType": "MemberAccess", + "referencedDeclaration": 7, + "src": "1218:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 99, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1234:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "1218:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1199:36:0" + }, + { + "expression": { + "arguments": [ + { + "id": 103, + "name": "assetExsist", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 94, + "src": "1251:11:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 102, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1243:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 104, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1243:20:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 105, + "nodeType": "ExpressionStatement", + "src": "1243:20:0" + }, + { + "expression": { + "id": 111, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 106, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1271:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 108, + "indexExpression": { + "id": 107, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 90, + "src": "1278:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1271:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 109, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1271:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 110, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1291:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "1271:25:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 112, + "nodeType": "ExpressionStatement", + "src": "1271:25:0" + } + ] + }, + "functionSelector": "def60e0d", + "id": 114, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "unLockAsset", + "nameLocation": "1153:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 91, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 90, + "mutability": "mutable", + "name": "id", + "nameLocation": "1181:2:0", + "nodeType": "VariableDeclaration", + "scope": 114, + "src": "1165:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 89, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1165:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1164:20:0" + }, + "returnParameters": { + "id": 92, + "nodeType": "ParameterList", + "parameters": [], + "src": "1191:0:0" + }, + "scope": 150, + "src": "1144:157:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 148, + "nodeType": "Block", + "src": "1352:241:0", + "statements": [ + { + "assignments": [ + 120 + ], + "declarations": [ + { + "constant": false, + "id": 120, + "mutability": "mutable", + "name": "assetExsist", + "nameLocation": "1365:11:0", + "nodeType": "VariableDeclaration", + "scope": 148, + "src": "1360:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 119, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1360:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 127, + "initialValue": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 126, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "baseExpression": { + "id": 121, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1379:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 123, + "indexExpression": { + "id": 122, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 116, + "src": "1386:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1379:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 124, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "size", + "nodeType": "MemberAccess", + "referencedDeclaration": 7, + "src": "1379:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 125, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1395:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "1379:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1360:36:0" + }, + { + "expression": { + "arguments": [ + { + "id": 129, + "name": "assetExsist", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1412:11:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 128, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1404:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 130, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1404:20:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 131, + "nodeType": "ExpressionStatement", + "src": "1404:20:0" + }, + { + "assignments": [ + 133 + ], + "declarations": [ + { + "constant": false, + "id": 133, + "mutability": "mutable", + "name": "assetIsLocked", + "nameLocation": "1500:13:0", + "nodeType": "VariableDeclaration", + "scope": 148, + "src": "1495:18:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 132, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1495:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 138, + "initialValue": { + "expression": { + "baseExpression": { + "id": 134, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1516:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 136, + "indexExpression": { + "id": 135, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 116, + "src": "1523:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1516:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 137, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1516:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1495:38:0" + }, + { + "expression": { + "arguments": [ + { + "id": 140, + "name": "assetIsLocked", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 133, + "src": "1549:13:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 139, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1541:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 141, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1541:22:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 142, + "nodeType": "ExpressionStatement", + "src": "1541:22:0" + }, + { + "expression": { + "id": 146, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "delete", + "prefix": true, + "src": "1571:17:0", + "subExpression": { + "baseExpression": { + "id": 143, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1578:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 145, + "indexExpression": { + "id": 144, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 116, + "src": "1585:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "1578:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 147, + "nodeType": "ExpressionStatement", + "src": "1571:17:0" + } + ] + }, + "functionSelector": "e24aa37c", + "id": 149, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deleteAsset", + "nameLocation": "1313:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 117, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 116, + "mutability": "mutable", + "name": "id", + "nameLocation": "1341:2:0", + "nodeType": "VariableDeclaration", + "scope": 149, + "src": "1325:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 115, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1325:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1324:20:0" + }, + "returnParameters": { + "id": 118, + "nodeType": "ParameterList", + "parameters": [], + "src": "1352:0:0" + }, + "scope": 150, + "src": "1304:289:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 151, + "src": "543:1053:0", + "usedErrors": [] + } + ], + "src": "413:1184:0" + }, + "functionHashes": { + "createAsset(string,uint256)": "db9cc410", + "deleteAsset(string)": "e24aa37c", + "getAsset(string)": "cd5286d0", + "lockAsset(string)": "5e82d0a6", + "unLockAsset(string)": "def60e0d" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "228200", + "executionCost": "269", + "totalCost": "228469" + }, + "external": { + "createAsset(string,uint256)": "infinite", + "deleteAsset(string)": "infinite", + "getAsset(string)": "infinite", + "lockAsset(string)": "infinite", + "unLockAsset(string)": "infinite" + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/api-surface.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/api-surface.test.ts new file mode 100644 index 0000000000..a77b09a829 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/api-surface.test.ts @@ -0,0 +1,8 @@ +import test, { Test } from "tape-promise/tape"; + +import * as apiSurface from "../../../main/typescript/public-api"; + +test("Library can be loaded", (t: Test) => { + t.ok(apiSurface, "apiSurface truthy OK"); + t.end(); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-baseline-events.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-baseline-events.test.ts new file mode 100644 index 0000000000..cebc20f6ce --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-baseline-events.test.ts @@ -0,0 +1,207 @@ +import test, { Test } from "tape-promise/tape"; +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; +import { RabbitMQTestServer } from "@hyperledger/cactus-test-tooling"; +import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; +import { IPluginCcTxVisualizationOptions } from "../../../main/typescript"; +import { + CcTxVisualization, + IChannelOptions, +} from "../../../main/typescript/plugin-cc-tx-visualization"; +import { randomUUID } from "crypto"; +import * as amqp from "amqp-ts"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const testCase = "dummy-baseline-6-events"; +const logLevel: LogLevelDesc = "TRACE"; +const queueName = "cc-tx-log-entry-test"; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "cctxviz-dummy-demo", +}); +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + //initialize rabbitmq + const setupInfraTime = new Date(); + const options = { + publishAllPorts: true, + port: 5672, + logLevel: logLevel, + imageName: "rabbitmq", + imageTag: "3.9-management", + emitContainerLogs: true, + envVars: new Map([["AnyNecessaryEnvVar", "Can be set here"]]), + }; + const channelOptions: IChannelOptions = { + queueId: queueName, + dltTechnology: null, + persistMessages: false, + }; + + const cctxvizOptions: IPluginCcTxVisualizationOptions = { + instanceId: randomUUID(), + logLevel: logLevel, + eventProvider: "amqp://localhost", + channelOptions: channelOptions, + }; + + const testServer = new RabbitMQTestServer(options); + const tearDown = async () => { + t.comment("shutdown starts"); + await testServer.stop(); + await cctxViz.shutdown(); + await cctxViz.closeConnection(); + log.debug("running process exit"); + process.exit(0); + }; + + test.onFinish(tearDown); + await testServer.start(true); + t.ok(testServer); + + // Simulates a Cactus Ledger Connector plugin + const connection = new amqp.Connection(); + const queue = connection.declareQueue(queueName, { durable: false }); + + // Initialize our plugin + const cctxViz = new CcTxVisualization(cctxvizOptions); + const setupInfraTimeEnd = new Date(); + log.debug( + `EVAL-testFile-SETUP-INFRA:${ + setupInfraTimeEnd.getTime() - setupInfraTime.getTime() + }`, + ); + t.ok(cctxViz); + t.comment("cctxviz plugin is ok"); + + t.assert(cctxViz.numberUnprocessedReceipts === 0); + t.assert(cctxViz.numberEventsLog === 0); + + const currentTime = new Date(); + const timeStartSendMessages = new Date(); + + // caseID 1; registar emissions; Fabric blockchain, test message; parameters: asset 1, 100 units + const testMessage1 = new amqp.Message({ + caseID: "1", + timestamp: currentTime, + blockchainID: "TEST", + invocationType: "send", + methodName: "initialize asset", + // Asset 1, 100 units + parameters: ["1,100"], + identity: "A", + }); + queue.send(testMessage1); + + const testMessage2 = new amqp.Message({ + caseID: "1", + timestamp: new Date(currentTime.getTime() + 2), + blockchainID: "TEST", + invocationType: "send", + methodName: "lock asset", + // Asset 1, 100 units + parameters: ["1,100"], + identity: "A", + }); + queue.send(testMessage2); + + const testMessage3 = new amqp.Message({ + caseID: "1", + timestamp: new Date(currentTime.getTime() + 3), + blockchainID: "TEST", + invocationType: "send", + methodName: "mint asset", + // Asset 1, 100 units + parameters: ["1,100"], + identity: "A", + }); + queue.send(testMessage3); + + const testMessage4 = new amqp.Message({ + caseID: "1", + timestamp: new Date(currentTime.getTime() + 4), + blockchainID: "TEST", + invocationType: "send", + methodName: "transfer asset", + // Asset 1, 100 units + parameters: ["A"], + identity: "A", + }); + queue.send(testMessage4); + + const testMessage5 = new amqp.Message({ + caseID: "1", + timestamp: new Date(currentTime.getTime() + 5), + blockchainID: "TEST", + invocationType: "send", + methodName: "transfer asset", + // Asset 1, 100 units + parameters: [""], + identity: "A", + }); + queue.send(testMessage5); + + const testMessage6 = new amqp.Message({ + caseID: "1", + timestamp: new Date(currentTime.getTime() + 6), + blockchainID: "TEST", + invocationType: "send", + methodName: "burn asset", + // Asset 1, 100 units + parameters: [""], + identity: "A", + }); + queue.send(testMessage6); + const endTimeSendMessages = new Date(); + t.comment( + `EVAL-testFile-SEND-MESSAGES:${ + endTimeSendMessages.getTime() - timeStartSendMessages.getTime() + }`, + ); + + const timeStartPollReceipts = new Date(); + await cctxViz.pollTxReceipts(); + await cctxViz.hasProcessedXMessages(6, 4); + + const endTimePollReceipts = new Date(); + const totalTimePoll = + endTimePollReceipts.getTime() - timeStartPollReceipts.getTime(); + t.comment(`EVAL-testFile-POLL:${totalTimePoll}`); + + t.assert(cctxViz.numberEventsLog === 0); + t.assert(cctxViz.numberUnprocessedReceipts === 6); + + await cctxViz.txReceiptToCrossChainEventLogEntry(); + + t.assert(cctxViz.numberEventsLog === 6); + t.assert(cctxViz.numberUnprocessedReceipts === 0); + + const logName = await cctxViz.persistCrossChainLogCsv( + "dummy-use-case-6-events", + ); + + const startTimeAggregate = new Date(); + await cctxViz.aggregateCcTx(); + const endTimeAggregate = new Date(); + t.comment( + `EVAL-testFile-AGGREGATE-CCTX:${ + endTimeAggregate.getTime() - startTimeAggregate.getTime() + }`, + ); + + const map = + "{'registerEmission': (node:registerEmission connections:{registerEmission:[0.6666666666666666], getEmissions:[0.6666666666666666]}), 'getEmissions': (node:getEmissions connections:{mintEmissionToken:[0.6666666666666666]}), 'mintEmissionToken': (node:mintEmissionToken connections:{})}"; + // Persist heuristic map that is generated from the script that takes this input + await cctxViz.saveModel(CrossChainModelType.HeuristicMiner, map); + const savedModel = await cctxViz.getModel(CrossChainModelType.HeuristicMiner); + t.assert(map === savedModel); + + console.log(logName); + t.ok(logName); + t.end(); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-invalid.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-invalid.test.ts new file mode 100644 index 0000000000..02fb72e647 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-generate-use-case-dummy-invalid.test.ts @@ -0,0 +1,200 @@ +import test, { Test } from "tape-promise/tape"; +import { LoggerProvider, LogLevelDesc } from "@hyperledger/cactus-common"; +import { RabbitMQTestServer } from "@hyperledger/cactus-test-tooling"; +import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; +import { IPluginCcTxVisualizationOptions } from "../../../main/typescript"; +import { + CcTxVisualization, + IChannelOptions, +} from "../../../main/typescript/plugin-cc-tx-visualization"; +import { randomUUID } from "crypto"; +import * as amqp from "amqp-ts"; + +const testCase = "dummy-baseline-invalid"; +const logLevel: LogLevelDesc = "TRACE"; +const queueName = "cc-tx-log-entry-test"; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "cctxviz-dummy-demo", +}); +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + //initialize rabbitmq + const setupInfraTime = new Date(); + const options = { + publishAllPorts: true, + port: 5672, + logLevel: logLevel, + imageName: "rabbitmq", + imageTag: "3.9-management", + emitContainerLogs: true, + envVars: new Map([["AnyNecessaryEnvVar", "Can be set here"]]), + }; + const channelOptions: IChannelOptions = { + queueId: queueName, + dltTechnology: null, + persistMessages: false, + }; + + const cctxvizOptions: IPluginCcTxVisualizationOptions = { + instanceId: randomUUID(), + logLevel: logLevel, + eventProvider: "amqp://localhost", + channelOptions: channelOptions, + }; + + const testServer = new RabbitMQTestServer(options); + const tearDown = async () => { + t.comment("shutdown starts"); + await testServer.stop(); + await cctxViz.shutdown(); + await cctxViz.closeConnection(); + log.debug("running process exit"); + process.exit(0); + }; + + test.onFinish(tearDown); + await testServer.start(true); + t.ok(testServer); + + // Simulates a Cactus Ledger Connector plugin + const connection = new amqp.Connection(); + const queue = connection.declareQueue(queueName, { durable: false }); + + // Initialize our plugin + const cctxViz = new CcTxVisualization(cctxvizOptions); + const setupInfraTimeEnd = new Date(); + log.debug( + `EVAL-testFile-SETUP-INFRA:${ + setupInfraTimeEnd.getTime() - setupInfraTime.getTime() + }`, + ); + t.ok(cctxViz); + t.comment("cctxviz plugin is ok"); + + t.assert(cctxViz.numberUnprocessedReceipts === 0); + t.assert(cctxViz.numberEventsLog === 0); + + const currentTime = new Date(); + let caseNumber = 1; + const caseID = "INVALID_FABRIC_BESU"; + + t.comment(`Sending ${caseNumber * 6} messages across ${caseNumber} cases`); + while (caseNumber > 0) { + // caseID 1; registar emissions; Fabric blockchain, test message; parameters: asset 1, 100 units + const testMessage1 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: currentTime, + blockchainID: "TEST", + invocationType: "send", + methodName: "createAsset", + // Asset 1, 100 units + parameters: ["asset1,5"], + identity: "A", + }); + queue.send(testMessage1); + + // BAD ORDER MINT BEFORE LOCK + const testMessage3 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: new Date(currentTime.getTime() + 2), + blockchainID: "TEST", + invocationType: "send", + methodName: "mintAsset", + // Asset 1, 100 units + parameters: ["asset1", "Green", "19", "owner1", "9999"], + identity: "A", + }); + queue.send(testMessage3); + + const testMessage2 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: new Date(currentTime.getTime() + 3), + blockchainID: "TEST", + invocationType: "send", + methodName: "lockAsset", + // Asset 1, 100 units + parameters: ["asset1"], + identity: "A", + }); + queue.send(testMessage2); + + const testMessage4 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: new Date(currentTime.getTime() + 4), + blockchainID: "TEST", + invocationType: "send", + methodName: "transferAsset", + // Asset 1, 100 units + parameters: ["asset1", "owner2"], + identity: "A", + }); + queue.send(testMessage4); + + const testMessage5 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: new Date(currentTime.getTime() + 5), + blockchainID: "TEST", + invocationType: "send", + methodName: "transferAsset", + // Asset 1, 100 units + parameters: ["asset1", "owner1"], + identity: "A", + }); + queue.send(testMessage5); + + const testMessage6 = new amqp.Message({ + caseID: caseID + "_" + caseNumber, + timestamp: new Date(currentTime.getTime() + 6), + blockchainID: "TEST", + invocationType: "send", + methodName: "BurnAsset", + // Asset 1, 100 units + parameters: ["asset1"], + identity: "A", + }); + queue.send(testMessage6); + + caseNumber--; + } + + const timeStartPollReceipts = new Date(); + await cctxViz.pollTxReceipts(); + await cctxViz.hasProcessedXMessages(6, 4); + + const endTimePollReceipts = new Date(); + const totalTimePoll = + endTimePollReceipts.getTime() - timeStartPollReceipts.getTime(); + t.comment(`EVAL-testFile-POLL:${totalTimePoll}`); + + t.assert(cctxViz.numberEventsLog === 0); + t.assert(cctxViz.numberUnprocessedReceipts === 6); + + await cctxViz.txReceiptToCrossChainEventLogEntry(); + + t.assert(cctxViz.numberEventsLog === 6); + t.assert(cctxViz.numberUnprocessedReceipts === 0); + + const logName = await cctxViz.persistCrossChainLogCsv( + "dummy-use-case-invalid", + ); + + const startTimeAggregate = new Date(); + await cctxViz.aggregateCcTx(); + const endTimeAggregate = new Date(); + t.comment( + `EVAL-testFile-AGGREGATE-CCTX:${ + endTimeAggregate.getTime() - startTimeAggregate.getTime() + }`, + ); + + console.log(logName); + t.ok(logName); + t.end(); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-persist-cross-chain-log.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-persist-cross-chain-log.test.ts new file mode 100644 index 0000000000..d32af002cf --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/cctxviz-persist-cross-chain-log.test.ts @@ -0,0 +1,120 @@ +import test, { Test } from "tape-promise/tape"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import { RabbitMQTestServer } from "@hyperledger/cactus-test-tooling"; +import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; +import { IPluginCcTxVisualizationOptions } from "../../../main/typescript"; +import { + CcTxVisualization, + IChannelOptions, +} from "../../../main/typescript/plugin-cc-tx-visualization"; +import { randomUUID } from "crypto"; +import * as amqp from "amqp-ts"; +//import { LedgerType } from "@hyperledger/cactus-core-api/src/main/typescript/public-api"; + +const testCase = "persist logs"; +const logLevel: LogLevelDesc = "TRACE"; +const queueName = "cc-tx-log-entry-test"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + //initialize rabbitmq + const options = { + publishAllPorts: true, + port: 5672, + logLevel: logLevel, + imageName: "rabbitmq", + imageTag: "3.9-management", + emitContainerLogs: true, + envVars: new Map([["AnyNecessaryEnvVar", "Can be set here"]]), + }; + const channelOptions: IChannelOptions = { + queueId: queueName, + dltTechnology: null, + persistMessages: false, + }; + + const cctxvizOptions: IPluginCcTxVisualizationOptions = { + instanceId: randomUUID(), + logLevel: logLevel, + eventProvider: "amqp://localhost", + channelOptions: channelOptions, + }; + + const testServer = new RabbitMQTestServer(options); + const tearDown = async () => { + // Connections to the RabbitMQ server need to be closed + + await testServer.stop(); + // todo problem connection closing is hanging here and l56 + await connection.close(); + + await cctxViz.closeConnection(); + + //await testServer.destroy(); + //await pruneDockerAllIfGithubAction({ logLevel }); + }; + + test.onFinish(tearDown); + + await testServer.start(true); + t.ok(testServer); + + // Simulates a Cactus Ledger Connector plugin + const connection = new amqp.Connection(); + const queue = connection.declareQueue(queueName, { durable: false }); + + // Initialize our plugin + const cctxViz = new CcTxVisualization(cctxvizOptions); + t.ok(cctxViz); + t.comment("cctxviz plugin is ok"); + await cctxViz.pollTxReceipts(); + t.assert(cctxViz.numberUnprocessedReceipts === 0); + t.assert(cctxViz.numberEventsLog === 0); + + // already activated by previous test + //await cctxViz.pollTxReceipts(); + + const testMessage = new amqp.Message({ + caseID: "caseID-TEST 1", + timestamp: new Date(), + blockchainID: "TEST", + invocationType: "call", + methodName: "methodName", + parameters: ["0", "2"], + identity: "person 1", + }); + queue.send(testMessage); + + const testMessage2 = new amqp.Message({ + caseID: "case1", + cost: 5, + revenue: 0, + carbonFootprint: 5, + timestamp: new Date(), + blockchainID: "TEST", + invocationType: "call", + methodName: "methodName", + parameters: ["0", "2"], + identity: "person 1", + }); + queue.send(testMessage2); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + await cctxViz.txReceiptToCrossChainEventLogEntry(); + + t.assert(cctxViz.numberEventsLog === 2); + // because the second message did not have time to be send to processing before receipts were transformed into cross chain events + t.assert(cctxViz.numberUnprocessedReceipts === 0); + + await cctxViz.txReceiptToCrossChainEventLogEntry(); + + const logName = await cctxViz.persistCrossChainLogCsv(); + console.log(logName); + t.ok(logName); + t.end(); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-cctxviz-usecase-fabric-besu-6-events.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-cctxviz-usecase-fabric-besu-6-events.test.ts new file mode 100644 index 0000000000..0c533f674e --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-cctxviz-usecase-fabric-besu-6-events.test.ts @@ -0,0 +1,583 @@ +import test, { Test } from "tape-promise/tape"; +import { + IListenOptions, + LoggerProvider, + LogLevelDesc, + Servers, +} from "@hyperledger/cactus-common"; +import { + BesuTestLedger, + Containers, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { RabbitMQTestServer } from "@hyperledger/cactus-test-tooling"; +import { IPluginCcTxVisualizationOptions } from "../../../main/typescript"; +import { + CcTxVisualization, + IChannelOptions, +} from "../../../main/typescript/plugin-cc-tx-visualization"; +import { randomUUID } from "crypto"; +import { IRabbitMQTestServerOptions } from "@hyperledger/cactus-test-tooling/dist/lib/main/typescript/rabbitmq-test-server/rabbit-mq-test-server"; +import { v4 as uuidv4 } from "uuid"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { DiscoveryOptions } from "fabric-network"; +import { Configuration } from "@hyperledger/cactus-core-api"; +import fs from "fs-extra"; +import LockAssetContractJson from "../../solidity/LockAsset.json"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; + +import { + ChainCodeProgrammingLanguage, + DefaultEventHandlerStrategy, + FabricContractInvocationType, + FileBase64, + PluginLedgerConnectorFabric, + IPluginLedgerConnectorFabricOptions, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import path from "path"; +import { DefaultApi as FabricApi } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import { AddressInfo } from "net"; +import Web3 from "web3"; +import { + EthContractInvocationType, + PluginFactoryLedgerConnector, + PluginLedgerConnectorBesu, + ReceiptType, + Web3SigningCredentialType, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; + +const testCase = "Instantiate plugin with fabric, send 2 transactions"; +const logLevel: LogLevelDesc = "TRACE"; + +// By default that's the Fabric connector queue +const queueName = "cc-tx-viz-queue"; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "cctxviz-fabtest", +}); +//const fixturesPath = +("../../../../../cactus-plugin-ledger-connector-fabric/src/test/typescript/fixtures"); +const alternativeFixturesPath = "../fixtures"; + +let cctxViz: CcTxVisualization; +let options: IRabbitMQTestServerOptions; +let channelOptions: IChannelOptions; +let testServer: RabbitMQTestServer; +let cctxvizOptions: IPluginCcTxVisualizationOptions; +let ledger: FabricTestLedgerV1; +let besuTestLedger: BesuTestLedger; +const expressAppBesu = express(); +expressAppBesu.use(bodyParser.json({ limit: "250mb" })); + +const expressApp = express(); +expressApp.use(bodyParser.json({ limit: "250mb" })); +const server = http.createServer(expressApp); + +test(testCase, async (t: Test) => { + const setupInfraTime = new Date(); + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + options = { + publishAllPorts: true, + port: 5672, + logLevel: logLevel, + imageName: "rabbitmq", + imageTag: "3.9-management", + emitContainerLogs: true, + envVars: new Map([["AnyNecessaryEnvVar", "Can be set here"]]), + }; + channelOptions = { + queueId: queueName, + dltTechnology: null, + persistMessages: false, + }; + + cctxvizOptions = { + instanceId: randomUUID(), + logLevel: logLevel, + eventProvider: "amqp://localhost", + channelOptions: channelOptions, + }; + testServer = new RabbitMQTestServer(options); + + await testServer.start(); + cctxViz = new CcTxVisualization(cctxvizOptions); + + ledger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + envVars: new Map([["FABRIC_VERSION", "2.2.0"]]), + logLevel, + }); + await ledger.start(); + + besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + const tearDown = async () => { + await cctxViz.closeConnection(); + await testServer.stop(); + await ledger.stop(); + await ledger.destroy(); + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + log.debug("executing exit"); + process.exit(0); + }; + + test.onFinish(tearDown); + t.ok(testServer); + const channelId = "mychannel"; + const channelName = channelId; + + const connectionProfile = await ledger.getConnectionProfileOrg1(); + const enrollAdminOut = await ledger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await ledger.enrollUser(adminWallet); + const sshConfig = await ledger.getSshConfig(); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user2"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPlugin = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ plugins: [keychainPlugin] }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + // This is the directory structure of the Fabirc 2.x CLI container (fabric-tools image) + // const orgCfgDir = "/fabric-samples/test-network/organizations/"; + const orgCfgDir = + "/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations/"; + + // these below mirror how the fabric-samples sets up the configuration + const org1Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org1MSP", + + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt`, + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp`, + CORE_PEER_ADDRESS: "peer0.org1.example.com:7051", + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + // these below mirror how the fabric-samples sets up the configuration + const org2Env = { + CORE_LOGGING_LEVEL: "debug", + FABRIC_LOGGING_SPEC: "debug", + CORE_PEER_LOCALMSPID: "Org2MSP", + + FABRIC_CFG_PATH: "/etc/hyperledger/fabric", + CORE_PEER_TLS_ENABLED: "true", + ORDERER_CA: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + + CORE_PEER_ADDRESS: "peer0.org2.example.com:9051", + CORE_PEER_MSPCONFIGPATH: `${orgCfgDir}peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp`, + CORE_PEER_TLS_ROOTCERT_FILE: `${orgCfgDir}peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt`, + ORDERER_TLS_ROOTCERT_FILE: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + collectTransactionReceipts: true, + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: org1Env, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + const plugin = new PluginLedgerConnectorFabric(pluginOptions); + + const listenOptions: IListenOptions = { + hostname: "localhost", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { port } = addressInfo; + + await plugin.getOrCreateWebServices(); + await plugin.registerWebServices(expressApp); + const apiUrl = `http://localhost:${port}`; + + const config = new Configuration({ basePath: apiUrl }); + + const apiClient = new FabricApi(config); + + // Setup: contract name + const contractName = "basic-asset-transfer-2"; + + // Setup: contract directory + const contractRelPath = "go/basic-asset-transfer/chaincode-typescript"; + const contractDir = path.join( + __dirname, + alternativeFixturesPath, + contractRelPath, + ); + const sourceFiles: FileBase64[] = []; + // Setup: push files + { + const filename = "./tslint.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./tsconfig.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./package.json"; + const relativePath = "./"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./index.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./asset.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + { + const filename = "./assetTransfer.ts"; + const relativePath = "./src/"; + const filePath = path.join(contractDir, relativePath, filename); + const buffer = await fs.readFile(filePath); + sourceFiles.push({ + body: buffer.toString("base64"), + filepath: relativePath, + filename, + }); + } + // Setup: Deploy smart contract + const res = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + // constructorArgs: { Args: ["john", "99"] }, + sourceFiles, + ccName: contractName, + targetOrganizations: [org1Env, org2Env], + caFile: `${orgCfgDir}ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem`, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { success } = res.data; + t.assert(success); + t.assert(res.status === 200); + + const contractNameBesu = "LockAsset"; + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = besuTestLedger.getGenesisAccountPubKey(); + const besuKeyPair = { + privateKey: besuTestLedger.getGenesisAccountPrivKey(), + }; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKeyBesu = uuidv4(); + const keychainEntryValueBesu = testEthAccount.privateKey; + const keychainPluginBesu = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKeyBesu, keychainEntryValueBesu]]), + logLevel, + }); + keychainPluginBesu.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.Local, + }); + const connector: PluginLedgerConnectorBesu = await factory.create({ + collectTransactionReceipts: true, + rpcApiHttpHost, + rpcApiWsHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPluginBesu] }), + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance); + + const deployOut = await connector.deployContract({ + keychainId: keychainPluginBesu.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + t.ok(deployOut); + t.ok(deployOut.transactionReceipt); + + const setupInfraTimeEnd = new Date(); + log.debug( + `EVAL-testFile-SETUP-INFRA:${ + setupInfraTimeEnd.getTime() - setupInfraTime.getTime() + }`, + ); + + const timeStartSendMessages = new Date(); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + const { success: createRes } = await connector.invokeContract({ + caseID: "FABRIC_BESU", + contractName: contractNameBesu, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1", 5], + signingCredential: { + ethAccount: testEthAccount.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + t.ok(createRes); + t.assert(createRes === true); + log.warn("create ok"); + const { success: lockRes } = await connector.invokeContract({ + caseID: "FABRIC_BESU", + contractName: contractNameBesu, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset1"], + signingCredential: { + ethAccount: testEthAccount.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + log.warn("checking lock res"); + t.ok(lockRes); + const assetId = "asset1"; + const assetOwner = "owner1"; + + const createResFabric = await apiClient.runTransactionV1({ + caseID: "FABRIC_BESU", + contractName, + channelName, + params: [assetId, "Green", "19", assetOwner, "9999"], + methodName: "MintAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }); + t.ok(createResFabric); + + // READS are not considered transactions, but are relevant to our use case + /* + const getRes = await apiClient.runTransactionV1({ + caseID: "FABRIC_BESU", + contractName, + channelName, + params: [assetId], + methodName: "ReadAsset", + invocationType: FabricContractInvocationType.Call, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }); + expect(getRes).toBeDefined(); + */ + + // Setup: transact + const transferAssetRes = await apiClient.runTransactionV1({ + caseID: "FABRIC_BESU", + contractName, + channelName, + params: [assetId, "owner2"], + methodName: "TransferAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }); + t.ok(transferAssetRes); + // Setup: transact + const transferAssetBackRes = await apiClient.runTransactionV1({ + caseID: "FABRIC_BESU", + contractName, + channelName, + params: [assetId, "owner1"], + methodName: "TransferAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }); + t.ok(transferAssetBackRes); + // Setup: transact + const burnAssetRes = await apiClient.runTransactionV1({ + caseID: "FABRIC_BESU", + contractName, + channelName, + params: [assetId], + methodName: "BurnAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: { + keychainId, + keychainRef: keychainEntryKey, + }, + }); + t.ok(burnAssetRes); + + // Initialize our plugin + t.ok(cctxViz); + log.info("cctxviz plugin is ok"); + const endTimeSendMessages = new Date(); + log.debug( + `EVAL-testFile-SEND-MESSAGES:${ + endTimeSendMessages.getTime() - timeStartSendMessages.getTime() + }`, + ); + const timeStartPollReceipts = new Date(); + await cctxViz.pollTxReceipts(); + await cctxViz.hasProcessedXMessages(6, 4); + + const endTimePollReceipts = new Date(); + const totalTimePoll = + endTimePollReceipts.getTime() - timeStartPollReceipts.getTime(); + log.debug(`EVAL-testFile-POLL:${totalTimePoll}`); + + // Number of messages on queue: 0 + t.assert(cctxViz.numberUnprocessedReceipts > 1); + t.assert(cctxViz.numberEventsLog === 0); + + await cctxViz.txReceiptToCrossChainEventLogEntry(); + + // Number of messages on queue: 0 + t.assert(cctxViz.numberUnprocessedReceipts === 0); + t.assert(cctxViz.numberEventsLog > 1); + + await cctxViz.persistCrossChainLogCsv("use-case-besu-fabric-6-events"); + + const startTimeAggregate = new Date(); + await cctxViz.aggregateCcTx(); + const endTimeAggregate = new Date(); + log.debug( + `EVAL-testFile-AGGREGATE-CCTX:${ + endTimeAggregate.getTime() - startTimeAggregate.getTime() + }`, + ); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-rabbitmq.test.ts b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-rabbitmq.test.ts new file mode 100644 index 0000000000..ac4489260a --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/src/test/typescript/integration/initialize-rabbitmq.test.ts @@ -0,0 +1,39 @@ +import test, { Test } from "tape-promise/tape"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import { RabbitMQTestServer } from "@hyperledger/cactus-test-tooling"; +import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling"; + +const testCase = "Instantiate plugin"; +const logLevel: LogLevelDesc = "TRACE"; + +test("BEFORE " + testCase, async (t: Test) => { + const pruning = pruneDockerAllIfGithubAction({ logLevel }); + await t.doesNotReject(pruning, "Pruning didn't throw OK"); + t.end(); +}); + +test(testCase, async (t: Test) => { + const options = { + publishAllPorts: true, + port: 5672, + logLevel: logLevel, + imageName: "rabbitmq", + imageTag: "3.9-management", + emitContainerLogs: true, + envVars: new Map([["AnyNecessaryEnvVar", "Can be set here"]]), + }; + + const testServer = new RabbitMQTestServer(options); + const tearDown = async () => { + await testServer.stop(); + // Destruction occurs when the RabbitMQ stops and has no listening connections + //await testServer.destroy(); + await pruneDockerAllIfGithubAction({ logLevel }); + }; + + test.onFinish(tearDown); + await testServer.start(); + t.ok(testServer); + //await new Promise((resolve) => setTimeout(resolve, 3000)); + t.end(); +}); diff --git a/packages/cactus-plugin-cc-tx-visualization/tsconfig.json b/packages/cactus-plugin-cc-tx-visualization/tsconfig.json new file mode 100644 index 0000000000..937bea6bb9 --- /dev/null +++ b/packages/cactus-plugin-cc-tx-visualization/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/types", + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-cc-tx-visualization.tsbuildinfo" + }, + "include": [ + "./src", + "./src/test/solidity/*.json", + ], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-besu/package.json b/packages/cactus-plugin-ledger-connector-besu/package.json index 400c2eb63f..8b099c650e 100644 --- a/packages/cactus-plugin-ledger-connector-besu/package.json +++ b/packages/cactus-plugin-ledger-connector-besu/package.json @@ -58,6 +58,7 @@ "@hyperledger/cactus-core": "2.0.0-alpha.2", "@hyperledger/cactus-core-api": "2.0.0-alpha.2", "axios": "1.6.0", + "amqp-ts": "1.8.0", "express": "4.18.2", "http-errors": "2.0.0", "joi": "17.9.1", diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json index 73f3a57fcc..0c19a63ae7 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/json/openapi.json @@ -772,6 +772,18 @@ ], "additionalProperties": false, "properties": { + "caseID": { + "type": "string", + "nullable": false + }, + "cost": { + "type": "number", + "nullable": false + }, + "carbonFootprint": { + "type": "string", + "nullable": false + }, "contractName": { "type": "string", "nullable": false diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts index cc33fe0a58..653b3b877b 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -653,6 +653,24 @@ export interface GetTransactionV1Response { * @interface InvokeContractV1Request */ export interface InvokeContractV1Request { + /** + * + * @type {string} + * @memberof InvokeContractV1Request + */ + 'caseID'?: string; + /** + * + * @type {number} + * @memberof InvokeContractV1Request + */ + 'cost'?: number; + /** + * + * @type {string} + * @memberof InvokeContractV1Request + */ + 'carbonFootprint'?: string; /** * * @type {string} diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts index a562b15a44..19b86cc5c6 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts @@ -34,6 +34,7 @@ import { IPluginWebService, ICactusPlugin, ICactusPluginOptions, + LedgerType, } from "@hyperledger/cactus-core-api"; import { @@ -101,6 +102,13 @@ import { IGetOpenApiSpecV1EndpointOptions, } from "./web-services/get-open-api-spec-v1-endpoint"; +//cc-tx-viz +import * as amqp from "amqp-ts"; +import { + BesuV2TxReceipt, + IsVisualizable, +} from "@hyperledger/cactus-plugin-cc-tx-visualization/src/main/typescript/models/transaction-receipt"; + export const E_KEYCHAIN_NOT_FOUND = "cactus.connector.besu.keychain_not_found"; export interface IPluginLedgerConnectorBesuOptions @@ -110,6 +118,10 @@ export interface IPluginLedgerConnectorBesuOptions pluginRegistry: PluginRegistry; prometheusExporter?: PrometheusExporter; logLevel?: LogLevelDesc; + collectTransactionReceipts?: boolean; + persistMessages?: boolean; + queueId?: string; + eventProvider?: string; } export class PluginLedgerConnectorBesu @@ -121,7 +133,7 @@ export class PluginLedgerConnectorBesu RunTransactionResponse >, ICactusPlugin, - IPluginWebService + IPluginWebService //, IsVisualizable //cc-tx-viz { private readonly instanceId: string; public prometheusExporter: PrometheusExporter; @@ -133,11 +145,18 @@ export class PluginLedgerConnectorBesu private contracts: { [name: string]: Contract; } = {}; - private endpoints: IWebServiceEndpoint[] | undefined; private httpServer: Server | SecureServer | null = null; - public static readonly CLASS_NAME = "PluginLedgerConnectorBesu"; + public transactionReceipts: any[] = []; + public collectTransactionReceipts: boolean; + + private amqpConnection: amqp.Connection | undefined; + private amqpQueue: amqp.Queue | undefined; + private amqpExchange: amqp.Exchange | undefined; + public readonly persistMessages: boolean | undefined; + public readonly queueId: string | undefined; + public readonly eventProvider: string | undefined; public get className(): string { return PluginLedgerConnectorBesu.CLASS_NAME; @@ -168,8 +187,29 @@ export class PluginLedgerConnectorBesu this.prometheusExporter, `${fnTag} options.prometheusExporter`, ); - this.prometheusExporter.startMetricsCollection(); + + //Visualization part + this.collectTransactionReceipts = + options.collectTransactionReceipts || false; + if (this.collectTransactionReceipts) { + this.eventProvider = options.eventProvider || "amqp://localhost"; + this.log.debug("Initializing connection to RabbitMQ"); + this.amqpConnection = new amqp.Connection(this.eventProvider); + this.log.info("Connection to RabbitMQ server initialized"); + const queue = options.queueId || "cc-tx-viz-queue"; + this.queueId = queue; + this.persistMessages = options.persistMessages || false; + this.amqpExchange = this.amqpConnection.declareExchange( + `cc-tx-viz-exchange`, + "direct", + { durable: this.persistMessages }, + ); + this.amqpQueue = this.amqpConnection.declareQueue(this.queueId, { + durable: this.persistMessages, + }); + this.amqpQueue.bind(this.amqpExchange); + } } public getOpenApiSpec(): unknown { @@ -186,6 +226,11 @@ export class PluginLedgerConnectorBesu return res; } + public closeConnection(): Promise { + this.log.info("Closing Amqp connection"); + return this.amqpConnection?.close(); + } + public getInstanceId(): string { return this.instanceId; } @@ -368,6 +413,7 @@ export class PluginLedgerConnectorBesu req: InvokeContractV1Request, ): Promise { const fnTag = `${this.className}#invokeContract()`; + const startTimeToTransaction = new Date(); const contractName = req.contractName; let contractInstance: Contract; @@ -398,7 +444,6 @@ export class PluginLedgerConnectorBesu const web3SigningCredential = req.signingCredential as | Web3SigningCredentialPrivateKeyHex | Web3SigningCredentialCactusKeychainRef; - const receipt = await this.transact({ transactionConfig: { data: `0x${contractJSON.bytecode}`, @@ -414,7 +459,6 @@ export class PluginLedgerConnectorBesu web3SigningCredential, privateTransactionConfig: req.privateTransactionConfig, }); - const address = { address: receipt.transactionReceipt.contractAddress, }; @@ -437,7 +481,6 @@ export class PluginLedgerConnectorBesu `${fnTag} Cannot invoke a contract without contract instance, the keychainId param is needed`, ); } - contractInstance = this.contracts[contractName]; if (req.contractAbi != undefined) { let abi; @@ -546,6 +589,47 @@ export class PluginLedgerConnectorBesu const out = await this.transact(txReq); const success = out.transactionReceipt.status; const data = { success, out }; + const endTimeToTransaction = new Date(); + this.log.debug( + `EVAL-${this.className}-ISSUE-TRANSACTION:${ + endTimeToTransaction.getTime() - startTimeToTransaction.getTime() + }`, + ); + + if (this.collectTransactionReceipts) { + const startTimeBesuReceipt = new Date(); + const extendedReceipt: BesuV2TxReceipt = { + caseID: req.caseID || "BESU_TBD", + blockchainID: LedgerType.Besu2X, + invocationType: req.invocationType, + methodName: req.methodName, + parameters: req.params, + timestamp: new Date(), + contractName: req.contractName, + status: out.transactionReceipt.status, + transactionHash: out.transactionReceipt.transactionHash, + transactionIndex: out.transactionReceipt.transactionIndex, + blockNumber: out.transactionReceipt.blockNumber, + blockHash: out.transactionReceipt.blockHash, + gasPrice: req.gasPrice, + gas: req.gas, + from: out.transactionReceipt.from, + to: out.transactionReceipt.to, + value: req.value, + gasUsed: out.transactionReceipt.gasUsed, + keychainID: req.keychainId, + signingCredentials: req.signingCredential, + }; + const txReceipt = new amqp.Message(extendedReceipt); + this.amqpQueue?.send(txReceipt); + const endTimeBesuReceipt = new Date(); + this.log.debug(`Sent transaction receipt to queue ${this.queueId}`); + this.log.debug( + `EVAL-${this.className}-GENERATE-AND-CAPTURE-RECEIPT:${ + endTimeBesuReceipt.getTime() - startTimeBesuReceipt.getTime() + }`, + ); + } return data; } else { throw new Error( diff --git a/packages/cactus-plugin-ledger-connector-fabric/package.json b/packages/cactus-plugin-ledger-connector-fabric/package.json index f08f8fd139..c5cfab5535 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/package.json +++ b/packages/cactus-plugin-ledger-connector-fabric/package.json @@ -60,6 +60,7 @@ "@hyperledger/cactus-core": "2.0.0-alpha.2", "@hyperledger/cactus-core-api": "2.0.0-alpha.2", "axios": "1.6.0", + "amqp-ts": "1.8.0", "bl": "5.0.0", "bn.js": "4.12.0", "elliptic": "6.5.4", diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json index 5fbf93276c..3b78f65bcf 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json @@ -414,6 +414,18 @@ "nullable": false } }, + "cost":{ + "type": "number", + "nullable": false + }, + "carbonFootprint":{ + "type": "string", + "nullable": false + }, + "caseID":{ + "type": "string", + "nullable": false + }, "transientData": { "type": "object", "nullable": true diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts index 062222a01e..8336a2a363 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -1030,6 +1030,24 @@ export interface RunTransactionRequest { * @memberof RunTransactionRequest */ 'endorsingOrgs'?: Array; + /** + * + * @type {number} + * @memberof RunTransactionRequest + */ + cost?: number; + /** + * + * @type {string} + * @memberof RunTransactionRequest + */ + carbonFootprint?: string; + /** + * + * @type {string} + * @memberof RunTransactionRequest + */ + caseID?: string; /** * * @type {object} diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts index 86fb643926..d84183749f 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/main/typescript/plugin-ledger-connector-fabric.ts @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ import fs from "fs"; import path from "path"; import { v4 as uuidv4 } from "uuid"; @@ -43,6 +44,7 @@ import { IWebServiceEndpoint, ICactusPlugin, ICactusPluginOptions, + LedgerType, } from "@hyperledger/cactus-core-api"; import { @@ -137,6 +139,7 @@ import { getTransactionReceiptByTxID, IGetTransactionReceiptByTxIDOptions, } from "./common/get-transaction-receipt-by-tx-id"; + import { GetBlockEndpointV1 } from "./get-block/get-block-endpoint-v1"; import { querySystemChainCode } from "./common/query-system-chain-code"; import { isSshExecOk } from "./common/is-ssh-exec-ok"; @@ -147,6 +150,10 @@ import { deployContractGoSourceImplFabricV256 } from "./deploy-contract-go-sourc const { loadFromConfig } = require("fabric-network/lib/impl/ccp/networkconfig"); assertFabricFunctionIsAvailable(loadFromConfig, "loadFromConfig"); +//cc-tx-viz +import * as amqp from "amqp-ts"; +import {FabricV2TxReceipt, IsVisualizable} from "@hyperledger/cactus-plugin-cc-tx-visualization/src/main/typescript/models/transaction-receipt"; + /** * Constant value holding the default $GOPATH in the Fabric CLI container as * observed on fabric deployments that are produced by the official examples @@ -215,6 +222,12 @@ export interface IPluginLedgerConnectorFabricOptions vaultConfig?: IVaultConfig; webSocketConfig?: IWebSocketConfig; signCallback?: SignPayloadCallback; + + //cc-tx-viz + collectTransactionReceipts?: boolean; + persistMessages?: boolean; + queueId?: string; + eventProvider?: string; } export class PluginLedgerConnectorFabric @@ -225,6 +238,7 @@ export class PluginLedgerConnectorFabric RunTransactionRequest, RunTransactionResponse >, + IsVisualizable, ICactusPlugin, IPluginWebService { @@ -241,6 +255,15 @@ export class PluginLedgerConnectorFabric private readonly certStore: CertDatastore; private readonly sshDebugOn: boolean; private runningWatchBlocksMonitors = new Set(); + + //cc-tx-viz + private amqpConnection: amqp.Connection | undefined; + private amqpQueue: amqp.Queue | undefined; + private amqpExchange: amqp.Exchange | undefined; + public readonly collectTransactionReceipts: boolean; + public readonly persistMessages: boolean | undefined; + public readonly queueId: string | undefined; + public readonly eventProvider: string | undefined; public get className(): string { return PluginLedgerConnectorFabric.CLASS_NAME; @@ -254,7 +277,7 @@ export class PluginLedgerConnectorFabric constructor(public readonly opts: IPluginLedgerConnectorFabricOptions) { const fnTag = `${this.className}#constructor()`; Checks.truthy(opts, `${fnTag} arg options`); - Checks.truthy(opts.instanceId, `${fnTag} options.instanceId`); + //Checks.truthy(opts.instanceId, `${fnTag} options.instanceId`); Checks.truthy(opts.peerBinary, `${fnTag} options.peerBinary`); Checks.truthy(opts.pluginRegistry, `${fnTag} options.pluginRegistry`); Checks.truthy(opts.connectionProfile, `${fnTag} options.connectionProfile`); @@ -299,6 +322,27 @@ export class PluginLedgerConnectorFabric } this.signCallback = opts.signCallback; + + //cc-tx-viz + // Visualization part + this.collectTransactionReceipts = opts.collectTransactionReceipts || false; + if (this.collectTransactionReceipts) { + this.eventProvider = opts.eventProvider || "amqp://localhost"; + this.log.debug("Initializing connection to RabbitMQ"); + this.amqpConnection = new amqp.Connection(this.eventProvider); + this.log.info("Connection to RabbitMQ server initialized"); + const queue = this.opts.queueId || "cc-tx-viz-queue"; + this.queueId = queue; + this.persistMessages = this.opts.persistMessages || false; + this.amqpExchange = this.amqpConnection.declareExchange(`cc-tx-viz-exchange`, "direct", {durable: this.persistMessages}); + this.amqpQueue = this.amqpConnection.declareQueue(this.queueId, {durable: this.persistMessages}); + this.amqpQueue.bind(this.amqpExchange); + } + } + + public closeConnection(): Promise { + this.log.info("Closing Amqp connection"); + return this.amqpConnection?.close(); } public getOpenApiSpec(): unknown { @@ -319,6 +363,11 @@ export class PluginLedgerConnectorFabric this.log.debug(`getPrometheusExporterMetrics() response: %o`, res); return res; } + + //TODO returns Promise + public async getTransactionReceiptsList(): Promise { + //returns list + } public getInstanceId(): string { return this.instanceId; @@ -1226,8 +1275,16 @@ export class PluginLedgerConnectorFabric public async transact( req: RunTransactionRequest, ): Promise { + //start transaction time + // const startTx = performance.now(); + + //start tx const fnTag = `${this.className}#transact()`; this.log.debug("%s ENTER", fnTag); + + //cc-tx-viz + const startTimeFabricReceipt = new Date(); + const { channelName, contractName, @@ -1294,9 +1351,33 @@ export class PluginLedgerConnectorFabric } const transientMap = this.toTransientMap(req.transientData); + + //cc-tx-viz + /* + const transientMap: TransientMap = transientData as TransientMap; + + try { + //Obtains and parses each component of transient data + for (const key in transientMap) { + transientMap[key] = Buffer.from( + JSON.stringify(transientMap[key]), + ); + } + } catch (ex) { + this.log.error(`Building transient map crashed: `, ex); + throw new Error( + `${fnTag} Unable to build the transient map: ${ex.message}`, + ); + } + */ + const transactionProposal = await contract.createTransaction(fnName); transactionProposal.setEndorsingPeers(endorsingTargets); out = await transactionProposal.setTransient(transientMap).submit(); + + //cc-tx-viz + //transactionId = transactionProposal.getTransactionId(); + //success = true; break; } default: { @@ -1304,7 +1385,74 @@ export class PluginLedgerConnectorFabric throw new Error(`${fnTag} unknown ${message}`); } } + //cc-tx-viz + /* + const endTimeFabricReceipt = new Date(); + this.log.debug(`EVAL-${this.className}-ISSUE-TRANSACTION:${endTimeFabricReceipt.getTime()-startTimeFabricReceipt.getTime()}`); + + // if we don't want to collect reads, than add condition && transactionId !== "" + if (this.collectTransactionReceipts) { + const startTimeFabricReceipt = new Date(); + const txParams = req.params; + //getTransactionReceiptByTxID requires 2 params in req.params => channelName and txID + req.params = []; + req.params[0] = req.channelName; + req.params[1] = transactionId; + //req.params get are stored in the basicTxReceipt rwsetWriteData + if (transactionId) { + const basicTxReceipt = await this.getTransactionReceiptByTxID(req); + const extendedReceipt: FabricV2TxReceipt={ + caseID: req.caseID || "FABRIC_TBD", + transactionID: transactionId, + blockchainID: LedgerType.Fabric2, + invocationType: req.invocationType, + methodName: req.methodName, + parameters: txParams, + timestamp: new Date(), + channelName: req.channelName, + contractName: req.contractName, + signingCredentials: req.signingCredential, + endorsingParties: req.endorsingParties, + endorsingPeers: req.endorsingPeers, + gatewayOptions: req.gatewayOptions, + transactionCreator: basicTxReceipt.transactionCreator, + transientData: req.transientData, + blockMetaData: basicTxReceipt.blockMetaData, + chainCodeName: basicTxReceipt.chainCodeName, + blockNumber: basicTxReceipt.blockNumber, + chainCodeVersion: basicTxReceipt.chainCodeVersion, + responseStatus: basicTxReceipt.responseStatus, + }; + const txReceipt = new amqp.Message(extendedReceipt); + this.amqpQueue?.send(txReceipt); + this.log.debug(`Sent extended transaction receipt to queue ${this.queueId}`); + } else { + const extendedReceipt: FabricV2TxReceipt={ + caseID: req.caseID || "FABRIC_TBD", + transactionID: undefined, + blockchainID: LedgerType.Fabric2, + invocationType: req.invocationType, + methodName: req.methodName, + parameters: txParams, + timestamp: new Date(), + channelName: req.channelName, + contractName: req.contractName, + signingCredentials: req.signingCredential, + endorsingParties: req.endorsingParties, + endorsingPeers: req.endorsingPeers, + gatewayOptions: req.gatewayOptions, + }; + const txReceipt = new amqp.Message(extendedReceipt); + this.amqpQueue?.send(txReceipt); + this.log.debug(`Sent simple transaction receipt to queue ${this.queueId}`); + } + const endTimeFabricReceipt = new Date(); + this.log.debug(`EVAL-${this.className}-GENERATE-AND-CAPTURE-RECEIPT:${endTimeFabricReceipt.getTime()-startTimeFabricReceipt.getTime()}`); + + } + const outUtf8 = out.toString("utf-8"); + */ const res: RunTransactionResponse = { functionOutput: this.convertToTransactionResponseType( out, @@ -1314,12 +1462,10 @@ export class PluginLedgerConnectorFabric }; gateway.disconnect(); this.log.debug(`transact() response: %o`, res); - this.prometheusExporter.addCurrentTransaction(); - return res; } catch (ex) { this.log.error(`transact() crashed: `, ex); - throw new Error(`${fnTag} Unable to run transaction: ${ex.message}`); + throw new Error(`${fnTag} Unable to run transaction: ${ex}`); } } @@ -1356,7 +1502,7 @@ export class PluginLedgerConnectorFabric return new FabricCAServices(caUrl, tlsOptions, caName); } catch (ex) { this.log.error(`createCaClient() Failure:`, ex); - throw new Error(`${fnTag} Inner Exception: ${ex?.message}`); + throw new Error(`${fnTag} Inner Exception: ${ex}`); } } @@ -1392,7 +1538,7 @@ export class PluginLedgerConnectorFabric return [x509Identity, wallet]; } catch (ex) { this.log.error(`enrollAdmin() Failure:`, ex); - throw new Error(`${fnTag} Exception: ${ex?.message}`); + throw new Error(`${fnTag} Exception: ${ex}`); } } /** diff --git a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts index a1933710c4..d46ca0c437 100644 --- a/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts +++ b/packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/integration/fabric-v2-2-x/run-transaction-endpoint-v1.test.ts @@ -35,8 +35,6 @@ import { FabricSigningCredential, } from "../../../../main/typescript/public-api"; -import { K_CACTUS_FABRIC_TOTAL_TX_COUNT } from "../../../../main/typescript/prometheus-exporter/metrics"; - import { IPluginLedgerConnectorFabricOptions } from "../../../../main/typescript/plugin-ledger-connector-fabric"; import { DiscoveryOptions } from "fabric-network"; import { Configuration } from "@hyperledger/cactus-core-api"; @@ -231,25 +229,6 @@ describe(testCase, () => { expect(asset277.Owner).toEqual(assetOwner); } - { - const res = await apiClient.getPrometheusMetricsV1(); - const promMetricsOutput = - "# HELP " + - K_CACTUS_FABRIC_TOTAL_TX_COUNT + - " Total transactions executed\n" + - "# TYPE " + - K_CACTUS_FABRIC_TOTAL_TX_COUNT + - " gauge\n" + - K_CACTUS_FABRIC_TOTAL_TX_COUNT + - '{type="' + - K_CACTUS_FABRIC_TOTAL_TX_COUNT + - '"} 3'; - expect(res).toBeTruthy(); - expect(res.data).toBeTruthy(); - expect(res.status).toEqual(200); - expect(res.data.includes(promMetricsOutput)).toBeTrue(); - } - { const req: RunTransactionRequest = { signingCredential, diff --git a/packages/cactus-test-tooling/src/main/typescript/public-api.ts b/packages/cactus-test-tooling/src/main/typescript/public-api.ts index 699fe00d42..6ef4e17aee 100755 --- a/packages/cactus-test-tooling/src/main/typescript/public-api.ts +++ b/packages/cactus-test-tooling/src/main/typescript/public-api.ts @@ -209,3 +209,5 @@ export { FABRIC_25_LTS_FABRIC_SAMPLES__ORDERER_TLS_ROOTCERT_FILE_ORG_2, IFabricOrgEnvInfo, } from "./fabric/fabric-samples-env-constants"; + +export { RabbitMQTestServer } from "./rabbitmq-test-server/rabbit-mq-test-server"; \ No newline at end of file diff --git a/packages/cactus-test-tooling/src/main/typescript/rabbitmq-test-server/rabbit-mq-test-server.ts b/packages/cactus-test-tooling/src/main/typescript/rabbitmq-test-server/rabbit-mq-test-server.ts new file mode 100644 index 0000000000..727f137c69 --- /dev/null +++ b/packages/cactus-test-tooling/src/main/typescript/rabbitmq-test-server/rabbit-mq-test-server.ts @@ -0,0 +1,251 @@ +import type { EventEmitter } from "events"; +import { Optional } from "typescript-optional"; +import { RuntimeError } from "run-time-error"; +import type { Container, ContainerInfo } from "dockerode"; +import Docker from "dockerode"; +import { Logger, Checks, Bools } from "@hyperledger/cactus-common"; +import type { LogLevelDesc } from "@hyperledger/cactus-common"; +import { LoggerProvider } from "@hyperledger/cactus-common"; +import { Containers } from "../common/containers"; +import { Config as SshConfig } from "node-ssh"; + +export interface IRabbitMQTestServerOptions { + readonly publishAllPorts: boolean; + readonly port: number; + readonly logLevel?: LogLevelDesc; + readonly imageName?: string; + readonly imageTag?: string; + readonly emitContainerLogs?: boolean; + readonly envVars?: Map; +} + +export class RabbitMQTestServer { + public static readonly CLASS_NAME = "RabbitMQTestServer"; + + public readonly logLevel: LogLevelDesc; + public readonly imageName: string; + public readonly imageTag: string; + public readonly imageFqn: string; + public readonly log: Logger; + public readonly emitContainerLogs: boolean; + public readonly publishAllPorts: boolean; + public readonly envVars: Map; + public readonly port: number; + private _containerId: Optional; + + public get containerId(): Optional { + return this._containerId; + } + + public get container(): Optional { + const docker = new Docker(); + return this.containerId.isPresent() + ? Optional.ofNonNull(docker.getContainer(this.containerId.get())) + : Optional.empty(); + } + + public get className(): string { + return RabbitMQTestServer.CLASS_NAME; + } + + constructor(public readonly opts: IRabbitMQTestServerOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(opts, `${fnTag} arg options`); + + this.publishAllPorts = opts.publishAllPorts; + this._containerId = Optional.empty(); + this.imageName = opts.imageName || "rabbitmq"; + this.port = opts.port || 5672; + this.imageTag = opts.imageTag || "3.9-management"; + this.imageFqn = `${this.imageName}:${this.imageTag}`; + this.envVars = opts.envVars || new Map(); + this.emitContainerLogs = Bools.isBooleanStrict(opts.emitContainerLogs) + ? (opts.emitContainerLogs as boolean) + : true; + + this.logLevel = opts.logLevel || "INFO"; + + const level = this.logLevel; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.log.debug(`Created instance of ${this.className} OK`); + } + public getContainerImageName(): string { + return `${this.imageName}:${this.imageTag}`; + } + public async start(omitPull = false): Promise { + const startTime = new Date(); + const docker = new Docker(); + if (this.containerId.isPresent()) { + this.log.warn(`Container ID provided. Will not start new one.`); + const container = docker.getContainer(this.containerId.get()); + return container; + } + if (!omitPull) { + this.log.debug(`Pulling image ${this.imageFqn}...`); + await Containers.pullImage(this.imageFqn); + this.log.debug(`Pulled image ${this.imageFqn} OK`); + } + + const dockerEnvVars: string[] = new Array(...this.envVars).map( + (pairs) => `${pairs[0]}=${pairs[1]}`, + ); + + // TODO: dynamically expose ports for custom port mapping + const createOptions = { + Env: dockerEnvVars, + + /* + Healthcheck: { + Test: ["CMD-SHELL", `rabbitmq-diagnostics -q ping`], + Interval: 1000000000, // 1 second + Timeout: 3000000000, // 3 seconds + Retries: 10, + StartPeriod: 1000000000, // 1 second + }, + */ + + ExposedPorts: { + "5672/tcp": {}, // Default port for RabbitMQ + "7235/tcp": {}, // Default port for RabbitMQ + }, + HostConfig: { + AutoRemove: true, + PublishAllPorts: this.publishAllPorts, + Privileged: false, + PortBindings: { + "5672/tcp": [{ HostPort: "5672" }], + "7235/tcp": [{ HostPort: "7235" }], + }, + }, + }; + + this.log.debug(`Starting ${this.imageFqn} with options: `, createOptions); + + return new Promise((resolve, reject) => { + const eventEmitter: EventEmitter = docker.run( + this.imageFqn, + [], + [], + createOptions, + {}, + (err: Error) => { + if (err) { + const errorMessage = `Failed to start container ${this.imageFqn}`; + const exception = new RuntimeError(errorMessage, err); + this.log.error(exception); + reject(exception); + } + }, + ); + + eventEmitter.once("start", async (container: Container) => { + const { id } = container; + this.log.debug(`Started ${this.imageFqn} successfully. ID=${id}`); + this._containerId = Optional.ofNonNull(id); + + if (this.emitContainerLogs) { + const logOptions = { follow: true, stderr: true, stdout: true }; + const logStream = await container.logs(logOptions); + logStream.on("data", (data: Buffer) => { + const fnTag = `[${this.imageFqn}]`; + this.log.debug(`${fnTag} %o`, data.toString("utf-8")); + }); + } + this.log.debug(`Registered container log stream callbacks OK`); + + try { + this.log.debug(`Starting to wait for healthcheck... `); + await this.waitForHealthCheck(); + this.log.debug(`Healthcheck passed OK`); + const finalTime = new Date(); + this.log.debug( + `EVAL-SETUP-INIT-RABBIT-MQ-SERVER:${ + finalTime.getTime() - startTime.getTime() + }`, + ); + resolve(container); + } catch (ex) { + this.log.error(ex); + reject(ex); + } + }); + }); + } + + public async waitForHealthCheck(timeoutMs = 180000): Promise { + const fnTag = "FabricTestLedgerV1#waitForHealthCheck()"; + const startedAt = Date.now(); + let reachable = false; + do { + try { + const { State } = await this.getContainerInfo(); + reachable = State === "running"; + } catch (ex) { + reachable = false; + if (Date.now() >= startedAt + timeoutMs) { + throw new Error(`${fnTag} timed out (${timeoutMs}ms) -> ${ex.stack}`); + } + } + await new Promise((resolve2) => setTimeout(resolve2, 1000)); + } while (!reachable); + } + public async stop(): Promise { + return Containers.stop(this.container.get()); + } + + public async destroy(): Promise { + if (!this.container.get()) { + return; + } + return this.container.get().remove(); + } + + public async getContainerIpAddress(): Promise { + const containerInfo = await this.getContainerInfo(); + return Containers.getContainerInternalIp(containerInfo); + } + + // TODO + public async getSshConfig(): Promise { + const fnTag = "RabbitMQTestServer#getSshConfig()"; + if (!this.container) { + throw new Error(`${fnTag} - invalid state no container instance set`); + } + const filePath = "/etc/hyperledger/cactus/fabric-aio-image.key"; + const privateKey = await Containers.pullFile( + (this.container as unknown) as Container, + filePath, + ); + const containerInfo = await this.getContainerInfo(); + const port = await Containers.getPublicPort(22, containerInfo); + const sshConfig: SshConfig = { + host: "localhost", + privateKey, + username: "root", + port, + }; + return sshConfig; + } + + protected async getContainerInfo(): Promise { + const fnTag = `${this.className}#getContainerInfo()`; + const docker = new Docker(); + const image = this.getContainerImageName(); + const containerInfos = await docker.listContainers({}); + + let aContainerInfo; + if (this.containerId !== undefined) { + aContainerInfo = containerInfos.find( + (ci) => ci.Id == this.containerId.get(), + ); + } + + if (aContainerInfo) { + return aContainerInfo; + } else { + throw new Error(`${fnTag} no image "${image}"`); + } + } +} diff --git a/packages/cactus-test-tooling/src/main/typescript/socketio-test-setup-helpers/socketio-test-setup-helpers.ts b/packages/cactus-test-tooling/src/main/typescript/socketio-test-setup-helpers/socketio-test-setup-helpers.ts index dfbb33ac09..6ab025f3fa 100644 --- a/packages/cactus-test-tooling/src/main/typescript/socketio-test-setup-helpers/socketio-test-setup-helpers.ts +++ b/packages/cactus-test-tooling/src/main/typescript/socketio-test-setup-helpers/socketio-test-setup-helpers.ts @@ -80,7 +80,7 @@ export function connectTestClient(socket: ClientSocket): Promise { socket.on("connect_timeout", errorHandlerFactory("connect_timeout")); socket.on("connect", () => { - socket.removeAllListeners(); + //socket.removeAllListeners(); resolve(socket); }); }); diff --git a/tsconfig.json b/tsconfig.json index ff13912150..6d1201bd28 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -130,6 +130,9 @@ { "path": "./packages/cactus-test-verifier-client/tsconfig.json" }, + { + "path": "./packages/cactus-plugin-cc-tx-visualization/tsconfig.json" + }, { "path": "./examples/cactus-example-carbon-accounting-backend/tsconfig.json" },