diff --git a/.cspell.json b/.cspell.json index 3037dab87e..41df3fe645 100644 --- a/.cspell.json +++ b/.cspell.json @@ -35,6 +35,8 @@ "cccs", "ccep", "ccid", + "ccmodel", + "cctx", "celo", "cids", "clazz", @@ -206,6 +208,7 @@ "undici", "unixfs", "Unmarshal", + "unmodeled", "utxoexample", "uuidv", "vscc", diff --git a/packages/cactus-plugin-ccmodel-hephaestus/.gitignore b/packages/cactus-plugin-ccmodel-hephaestus/.gitignore new file mode 100644 index 0000000000..fc4c178418 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/.gitignore @@ -0,0 +1,3 @@ +cactus-openapi-spec-plugin-consortium-manual.json +src/main/typescript/generated/openapi/typescript-axios/.npmignore +src/test/ccLogs \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/README.md b/packages/cactus-plugin-ccmodel-hephaestus/README.md new file mode 100644 index 0000000000..1ca4602828 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/README.md @@ -0,0 +1,134 @@ +# `@hyperledger/cactus-plugin-ccmodel-hephaestus` + +The package provides `Hyperledger Cacti` a way to generate process models from arbitrary cross-chain use cases. The implementation follows the paper [Hephaestus](https://www.techrxiv.org/doi/full/10.36227/techrxiv.20718058.v3). + +With this plugin it will be possible to generate cross-chain models from local transactions in different ledgers (currently supports Besu, Ethereum, and Fabric), realizing arbitrary cross-chain use cases and allowing operators to monitor their applications. +Through monitoring, errors like outliers and malicious behavior can be identified, which can enable programmatically stopping attacks (circuit breaker), including bridge hacks. + +## Summary + +- [`@hyperledger/cactus-plugin-ccmodel-hephaestus`](#hyperledgercactus-plugin-ccmodel-hephaestus) + - [Summary](#summary) + - [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Architecture](#architecture) + - [RxJS Transaction Monitoring](#rxjs-transaction-monitoring) + - [Cross-Chain Model Pipeline](#cross-chain-model-pipeline) + - [PM4Py Overview](#pm4py-overview) + - [Running the tests](#running-the-tests) + - [Usage](#usage) + - [Contributing](#contributing) + - [License](#license) + + +## Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites + +In the root of the project to install the dependencies execute the command: +```sh +npm run configure +``` + +The plugin requires Python and packages for process mining functionality. Install the following prerequisites: +1. Python 3.x and pip: +```sh +sudo apt install python3 python3-pip +``` +2. Required Python packages: +```sh +pip3 install pm4py pandas +``` + +Know how to use the following plugins of the project: + + - [cactus-plugin-ledger-connector-besu](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-besu) + - [cactus-plugin-ledger-connector-ethereum](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-ethereum) + - [cactus-plugin-ledger-connector-fabric](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-ledger-connector-fabric) + + +## Architecture + +### RxJS Transaction Monitoring + +This plugin utilizes RxJS (Reactive Extensions for JavaScript) to monitor transactions issued in Hyperledger Besu, Hyperledger Ethereum, and Hyperledger Fabric connectors. + +RxJS provides a powerful framework for asynchronous or callback-based code, each connector maintains an RxJS `ReplaySubject`, named `txSubject`, which acts as a message bus for emitting transaction data. + +- When a transaction is issued in a connector, the `ReplaySubject` stores the value it observes in an internal buffer. This observation is achieved by passing the value to its `next` method. +- When a new subscriber subscribes to the `txSubject`, it synchronously emits all values in its buffer in a First-In-First-Out (FIFO) manner. This ensures that subscribers receive the most recent transactional data, regardless of when they subscribe. +- The transactional data emitted includes essential information such as the transaction ID, timestamp, and other parameters, which are necessary for creating transaction receipts within the plugin. + +### Cross-Chain Model Pipeline + +The plugin employs a structured pipeline to create a cross-chain model from monitored connector transactions: + +1. **Transaction Emission**: Connectors issue local transactions against their respective target blockchains. Each transaction's data is emitted by the `txSubject` to the subscribers within our plugin. + +2. **Receipt Polling**: Upon receiving of transactional data, the plugin processes it into transaction receipts. This step involves precessing transactional information received such as transaction IDs, timestamps, and other parameters. + +3. **Cross-Chain Event Logging**: Processed receipts information can then be used to create cross-chain events, forming a cross-chain event log. + +4. **Cross-Chain Model**: The plugin uses the cross chain event log to create the cross-chain model with the information received from the connectors, using the Process Mining for Python (PM4PY) library. This model can then be used to verify new and unmodeled transactional information received from the connectors. + +### PM4Py Overview + +[PM4Py](https://pm4py.fit.fraunhofer.de/) is an open-source library designed for process mining, that provides tools to analyze and visualize business processes. +With it we can generate process models from the event logs created from transactional data and compare event logs with process models to identify deviations. + +## Running the tests + - **monitor-4-besu-events.test.ts**: Tests the plugin's ability to monitor, capture, and process the transactional data from besu connectors, and exports transactional data, in both CSV and JSON formats, as cross-chain event logs. + - **monitor-4-ethereum-events.test.ts**: Tests the plugin's ability to monitor, capture, and process the transactional data from ethereum connectors, and exports transactional data, in both CSV and JSON formats, as cross-chain event logs. + - **monitor-4-fabric-events.test.ts**: Tests the plugin's ability to monitor, capture, and process the transactional data from fabric connectors, and exports transactional data, in both CSV and JSON formats, as cross-chain event logs. + - **cross-chain-model-serialization.test.ts**: Tests the plugin's ability to create the cross-chain model using the PM4PY (Process Mining for Python) library and save it serialized. + - **cross-chain-model-conformance-checking.test.ts**: Tests the plugin's ability to check the conformity of unmodeled transactions against the cross-chain model using the PM4PY (Process Mining for Python) library. + - **besu-ethereum-asset-transfer.test.ts**: Tests the plugin's ability to detect non-conformance in asset transactions across two ledgers. + - **ethereum-fabric-asset-transfer.test.ts**: Tests the plugin's ability to detect non-conformance in asset transactions across two ledgers. + - **fabric-besu-asset-transfer.test.ts**: Tests the plugin's ability to detect non-conformance in asset transactions across two ledgers. + +## Usage +Let us consider two conectors: one connected to Hyperledger Besu and, and another connected to Hyperledger Ethereum. To monitor cross-chain transactions and create a cross-chain model we should follow the next steps. + +After instantiating the connectors, we instantiate the plugin as follows: +```typescript +let hephaestusOptions: IPluginCcModelHephaestusOptions; + +hephaestusOptions = { + instanceId: randomUUID(), + logLevel: logLevel, + besuTxObservable: besuConnector.getTxSubjectObservable(), + ethereumTxObservable: ethereumConnector.getTxSubjectObservable(), + sourceLedger: LedgerType.Besu2X, + targetLedger: LedgerType.Ethereum, +}; +hephaestus = new CcModelHephaestus(hephaestusOptions); +``` + +We set the desired caseID and start monitoring transactions. These are then processed into cross-chain events when received and added to the cross-chain event log: + +```typescript +hephaestus.setCaseId("Desired_CaseID"); +hephaestus.monitorTransactions(); +``` + +We can create the cross-chain model with events created from the transactional data captured: + +```typescript +await hephaestus.createModel(); +``` + +After creating a model with the desired event logs, we can turn off modeling so that newly received transactional data will be compared against the model through a conformance check: + +```typescript +hephaestus.setIsModeling(false); +``` + +## Contributing +We welcome contributions to Hyperledger Cactus in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](https://github.com/hyperledger/cactus/blob/main/CONTRIBUTING.md "CONTIRBUTING.md") to get started. + +## License +This distribution is published under the Apache License Version 2.0 found in the [LICENSE ](https://github.com/hyperledger/cactus/blob/main/LICENSE "LICENSE ")file. \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/package.json b/packages/cactus-plugin-ccmodel-hephaestus/package.json new file mode 100644 index 0000000000..4f06a2036d --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/package.json @@ -0,0 +1,99 @@ +{ + "name": "@hyperledger/cactus-plugin-ccmodel-hephaestus", + "version": "2.0.0", + "description": "A web service plugin that provides management capabilities on cross-chain transactions visualization.", + "keywords": [ + "Hyperledger", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cactus#readme", + "bugs": { + "url": "https://github.com/hyperledger/cactus/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cactus.git" + }, + "license": "Apache-2.0", + "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" + }, + { + "name": "Bruno Mateus" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cactus-plugin-ccmodel-hephaestus.web.umd.js", + "types": "dist/types/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "build": "npm run tsc", + "tsc": "tsc --project ./tsconfig.json", + "watch": "npm-watch", + "webpack": "npm-run-all webpack:dev webpack:prod", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js", + "webpack:prod": "npm-run-all webpack:prod:node webpack:prod:web", + "webpack:prod:node": "webpack --env=prod --target=node --config ../../webpack.config.js", + "webpack:prod:web": "webpack --env=prod --target=web --config ../../webpack.config.js" + }, + "dependencies": { + "@hyperledger/cactus-common": "2.0.0", + "@hyperledger/cactus-core": "2.0.0", + "@hyperledger/cactus-core-api": "2.0.0", + "@hyperledger/cactus-plugin-ledger-connector-besu": "2.0.0", + "@hyperledger/cactus-plugin-ledger-connector-ethereum": "2.0.0", + "@hyperledger/cactus-plugin-ledger-connector-fabric": "2.0.0", + "express": "4.21.0", + "run-time-error-cjs": "1.4.0", + "rxjs": "7.8.1", + "typescript-optional": "2.0.1", + "uuid": "10.0.0" + }, + "devDependencies": { + "@hyperledger/cactus-plugin-keychain-memory": "2.0.0", + "@hyperledger/cactus-test-geth-ledger": "2.0.0", + "@hyperledger/cactus-test-tooling": "2.0.0", + "@types/express": "5.0.0", + "@types/uuid": "10.0.0", + "body-parser": "1.20.3", + "fabric-network": "2.2.20", + "socket.io": "4.6.2", + "uuid": "10.0.0", + "web3": "1.6.1", + "web3-core": "1.6.1" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cactus-plugin-ccmodel-hephaestus.web.umd.min.js", + "mainMinified": "dist/cactus-plugin-ccmodel-hephaestus.node.umd.min.js" +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/check_conformance.py b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/check_conformance.py new file mode 100644 index 0000000000..61ed7dfac9 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/check_conformance.py @@ -0,0 +1,206 @@ +import sys +import os +# uncomment if problems with dependencies +#%pip install pm4py +#%pip install pandas +import pm4py +import time +import pandas +import pickle +import json +from pm4py.objects.petri_net.obj import PetriNet, Marking +from pm4py.objects.petri_net.utils import petri_utils + +################################################################## + +def import_csv_original(file_path): + event_log = pandas.read_csv(file_path, sep=';') + event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp') + return event_log + +def import_json_original(file_path): + with open(file_path, 'r') as file: + data = json.load(file) + event_log = pandas.DataFrame(data) + event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp') + return event_log + +################################################################## + +def divide_model(model): + split_model = model.split(';') + return split_model[0], split_model[1], split_model[2], split_model[3], split_model[4] + +def get_from_to_arc(arc): + if '->' in arc: + return arc.split('->') + else: + return None, None + +def get_place(places_set, name): + for place in places_set: + if place.name == name: + return place + return None + +def get_transition(transitions_set, name): + name = name[1:name.find(',')] + for transition in transitions_set: + if transition.name == name: + return transition + return None + +def unserialize_model(model): + (places_str, transitions_str, arcs_str, im_str, fm_str) = divide_model(model) + + net = PetriNet("unserialized__petri_net") + + #unserialize places: + places_str = places_str[1:-1].replace(" ", "") + places_str_list = places_str.split(',') # creates a list of stringed places + for place_str in places_str_list: + net.places.add(PetriNet.Place(place_str)) # creates place from each string + + #unserialize transitions: + transitions_str = transitions_str[1:-1].replace(" ", "").replace("(", "").replace(")", "") + transitions_str_list = transitions_str.split(',') # creates a list of strings + i = 0 + while i < len(transitions_str_list): + if transitions_str_list[i] == 'None': + net.transitions.add(PetriNet.Transition(None, transitions_str_list[i+1])) + elif transitions_str_list[i+1] == 'None': + net.transitions.add(PetriNet.Transition(transitions_str_list[i], None)) + else: + net.transitions.add(PetriNet.Transition(transitions_str_list[i], transitions_str_list[i+1])) + i += 2 + + #unserialize arcs: + arcs_str = arcs_str[1:-1].replace(" ", "") + text_parsed = arcs_str + arcs_list = [] + while True: + next_parentisis = text_parsed.find(')') + next_comma = text_parsed.find(',', next_parentisis, len(text_parsed)) + + if next_parentisis != -1 and next_comma != -1: + arcs_list.append(text_parsed[:next_comma]) + text_parsed = text_parsed[next_comma + 1:] + else: + break + arcs_list.append(text_parsed[:len(text_parsed)]) + + # creates arc from place names + for arc in arcs_list: + (source, target) = get_from_to_arc(arc) + + # source is a place, target is a transition + if get_place(net.places, source) != None: + place = get_place(net.places, source) + transition = get_transition(net.transitions, target) + petri_utils.add_arc_from_to(place, transition, net) + # target is a place, source is a transition + elif get_place(net.places, target) != None: + transition = get_transition(net.transitions, source) + place = get_place(net.places, target) + petri_utils.add_arc_from_to(transition, place, net) + # target and source are both a transition or a place - cannot happen + else: + print("arcs cannot have the same type in source and target") + exit(1) + + ### unserialize tokens + im_info = im_str.replace("[", "").replace("]", "").replace("\'", "").split(':') + fm_info = fm_str.replace("[", "").replace("]", "").replace("\'", "").split(':') + initial_marking = Marking() + im_place = get_place(net.places, im_info[0]) + fm_place = get_place(net.places, fm_info[0]) + initial_marking[im_place] = int(im_info[1]) + final_marking = Marking() + final_marking[fm_place] = int(fm_info[1]) + + return net, initial_marking, final_marking + +def unserialize_and_check_conformance(ccLog): + (net, initial_marking, final_marking) = unserialize_model(serialized_ccmodel) + # pm4py.view_petri_net(net, initial_marking, final_marking) + + # check conformance: + diagnostics = pm4py.conformance_diagnostics_alignments(ccLog, net, initial_marking, final_marking) + if diagnostics == []: + print("No event log provided") + return + + alignment = diagnostics[0]["alignment"] + conforming_activities = [] + non_conforming_activities = [] + skipped_activities = [] + all_activities = [] + + for activity in alignment: + if activity[0] == ">>" and activity[1] != None: + all_activities.append(activity) + skipped_activities.append(activity) + elif activity[0] != ">>" and activity[1] == ">>": + all_activities.append(activity) + non_conforming_activities.append(activity) + elif activity[0] != None and activity[1] != None: + all_activities.append(activity) + conforming_activities.append(activity) + + # Check for non-confomant behaviour + if len(non_conforming_activities) != 0: + print("NON-CONFORMANCE:") + print(non_conforming_activities) + print(os.path.basename(log_file_path)) + return + + if len(all_activities) == len(conforming_activities): + print("FULL CONFORMANCE:") + print(conforming_activities) + print(os.path.basename(log_file_path)) + return + + # If there were no skips in the case, then all the conforming activities + # will be the same as the initial activities of the model + # If not, then there were skips that cannot be ignored + ignore_skips = True + for i in range(len(conforming_activities)): + if(conforming_activities[i] != all_activities[i]): + ignore_skips = False + + if ignore_skips == True: + print("PARTIAL CONFORMANCE:") + print(conforming_activities) + print(os.path.basename(log_file_path)) + else: + print("SKIPPED ACTIVITY:") + print(skipped_activities) + print(os.path.basename(log_file_path)) + +################################################################## + +def main(): + if not os.path.exists(log_file_path): + print(f"File '{log_file_path}' does not exist") + exit(1) + + file_extension = os.path.splitext(log_file_path)[1].lower() + + if file_extension == '.csv': + ccLog = import_csv_original(log_file_path) + unserialize_and_check_conformance(ccLog) + elif file_extension == '.json': + ccLog = import_json_original(log_file_path) + unserialize_and_check_conformance(ccLog) + else: + print(f"Unsupported file type: {file_extension}") + exit(1) + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: python3 check_conformance.py path_to_log_file serialized_ccmodel") + exit(1) + + log_file_path = sys.argv[1] + serialized_ccmodel = sys.argv[2] + main() \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/convert_model.py b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/convert_model.py new file mode 100644 index 0000000000..e2f528a8e3 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/convert_model.py @@ -0,0 +1,118 @@ +import sys +# uncomment if problems with dependencies +#%pip install pm4py +import pm4py +from pm4py.objects.petri_net.obj import PetriNet, Marking +from pm4py.objects.petri_net.utils import petri_utils + +################################################################## + +def divide_model(model): + split_model = model.split(';') + return split_model[0], split_model[1], split_model[2], split_model[3], split_model[4] + +def get_from_to_arc(arc): + if '->' in arc: + return arc.split('->') + else: + return None, None + +def get_place(places_set, name): + for place in places_set: + if place.name == name: + return place + return None + +def get_transition(transitions_set, name): + name = name[1:name.find(',')] + for transition in transitions_set: + if transition.name == name: + return transition + return None + +def unserialize_model(model): + (places_str, transitions_str, arcs_str, im_str, fm_str) = divide_model(model) + + net = PetriNet("unserialized__petri_net") + + #unserialize places: + places_str = places_str[1:-1].replace(" ", "") + places_str_list = places_str.split(',') # creates a list of stringed places + for place_str in places_str_list: + net.places.add(PetriNet.Place(place_str)) # creates place from each string + + #unserialize transitions: + transitions_str = transitions_str[1:-1].replace(" ", "").replace("(", "").replace(")", "") + transitions_str_list = transitions_str.split(',') # creates a list of strings + i = 0 + while i < len(transitions_str_list): + if transitions_str_list[i] == 'None': + net.transitions.add(PetriNet.Transition(None, transitions_str_list[i+1])) + elif transitions_str_list[i+1] == 'None': + net.transitions.add(PetriNet.Transition(transitions_str_list[i], None)) + else: + net.transitions.add(PetriNet.Transition(transitions_str_list[i], transitions_str_list[i+1])) + i += 2 + + #unserialize arcs: + arcs_str = arcs_str[1:-1].replace(" ", "") + text_parsed = arcs_str + arcs_list = [] + while True: + next_parentisis = text_parsed.find(')') + next_comma = text_parsed.find(',', next_parentisis, len(text_parsed)) + + if next_parentisis != -1 and next_comma != -1: + arcs_list.append(text_parsed[:next_comma]) + text_parsed = text_parsed[next_comma + 1:] + else: + break + arcs_list.append(text_parsed[:len(text_parsed)]) + + # creates arc from place names + for arc in arcs_list: + (source, target) = get_from_to_arc(arc) + + # source is a place, target is a transition + if get_place(net.places, source) != None: + place = get_place(net.places, source) + transition = get_transition(net.transitions, target) + petri_utils.add_arc_from_to(place, transition, net) + + # target is a place, source is a transition + elif get_place(net.places, target) != None: + transition = get_transition(net.transitions, source) + place = get_place(net.places, target) + petri_utils.add_arc_from_to(transition, place, net) + + # target and source are both a transition or a place - cannot happen + else: + print("arcs cannot have the same type in source and target") + exit(1) + + ### unserialize tokens + im_info = im_str.replace("[", "").replace("]", "").replace("\'", "").split(':') + fm_info = fm_str.replace("[", "").replace("]", "").replace("\'", "").split(':') + initial_marking = Marking() + im_place = get_place(net.places, im_info[0]) + fm_place = get_place(net.places, fm_info[0]) + initial_marking[im_place] = int(im_info[1]) + final_marking = Marking() + final_marking[fm_place] = int(fm_info[1]) + + return net, initial_marking, final_marking + +################################################################## + +def main(): + (petri_net, initial_marking, final_marking) = unserialize_model(serialized_model) + process_tree = pm4py.convert_to_process_tree(petri_net, initial_marking, final_marking) + print(process_tree) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python3 convert_model.py serialized_model") + exit(1) + + serialized_model = sys.argv[1] + main() \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/create_model.py b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/create_model.py new file mode 100644 index 0000000000..7f06e68842 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/python/create_model.py @@ -0,0 +1,59 @@ +import sys +import os +# uncomment if problems with dependencies +#%pip install pm4py +#%pip install pandas +import pm4py +import time +import pandas +import pickle +import json + +def import_csv_original(file_path): + event_log = pandas.read_csv(file_path, sep=';') + event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp') + return event_log + +def import_json_original(file_path): + with open(file_path, 'r') as file: + data = json.load(file) + event_log = pandas.DataFrame(data) + event_log = pm4py.format_dataframe(event_log, case_id='caseID', activity_key='methodName', timestamp_key='timestamp') + return event_log + +################################################################## + +def create_and_serialize_model(ccLog): + pn, im, fm = pm4py.discover_petri_net_inductive(ccLog) + # pm4py.view_petri_net(pn, im, fm) + return str(pn.places) + ";" + str(pn.transitions) + ";" + str(pn.arcs) + ";" + str(im) + ";" + str(fm) + +################################################################## + +def main(): + file_path = sys.argv[1] + + if not os.path.exists(file_path): + print(f"File '{file_path}' does not exist") + exit(1) + + file_extension = os.path.splitext(file_path)[1].lower() + + if file_extension == '.csv': + ccLog = import_csv_original(file_path) + serialized_model = create_and_serialize_model(ccLog) + print(serialized_model) + elif file_extension == '.json': + ccLog = import_json_original(file_path) + serialized_model = create_and_serialize_model(ccLog) + print(serialized_model) + else: + print(f"Unsupported file type: {file_extension}") + exit(1) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python3 create_model.py path_to_log_file") + exit(1) + + main() \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/ccmodel-adapter.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/ccmodel-adapter.ts new file mode 100644 index 0000000000..d3e7f25eb2 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/ccmodel-adapter.ts @@ -0,0 +1,41 @@ +import { execSync } from "child_process"; + +import path from "path"; + +export function createModelPM4PY(logPath: string): string { + const createModelScript = path.join(__dirname, "../python/create_model.py"); + const command = `python3 ${createModelScript} ${logPath}`; + + try { + const startTime = new Date(); + const serializedCCModel = execSync(command).toString("utf-8"); + const finalTime = new Date(); + console.log( + `CREATE-MODEL-PM4PY:${finalTime.getTime() - startTime.getTime()}`, + ); + return serializedCCModel; + } catch (error) { + console.error(`Error executing ${command}:`, error); + throw error; + } +} + +export function checkConformancePM4PY( + logPath: string, + serializedCCModel: string, +): string { + const checkConformanceScript = path.join( + __dirname, + "../python/check_conformance.py", + ); + const command = `python3 ${checkConformanceScript} ${logPath} \'${serializedCCModel}\'`; + + // console.log(command); + try { + const checkOutput = execSync(command).toString("utf-8"); + return checkOutput; + } catch (error) { + console.error(`Error executing ${command}:`, error); + throw error; + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.web.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.web.ts new file mode 100644 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/carbon-footprint.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/carbon-footprint.ts new file mode 100644 index 0000000000..e71c4c3613 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/carbon-footprint.ts @@ -0,0 +1,40 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; + +export function calculateGasPriceEth( + gasUsed: number, + effectiveGasPrice: string | undefined, +): number { + if (!gasUsed || !effectiveGasPrice) { + return 0; + } + const gasPrice = parseFloat(effectiveGasPrice); + return gasUsed * gasPrice; +} + +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; + + default: + return 0; + } +}; diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/cross-chain-event.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/cross-chain-event.ts new file mode 100644 index 0000000000..6c857a6544 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/cross-chain-event.ts @@ -0,0 +1,78 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; + +export type CrossChainEvent = { + caseID: string; + receiptID: string; + timestamp: string; + blockchainID: LedgerType; + invocationType: string; + methodName: string; + parameters: string[]; + identity: string; + cost?: number | string; + carbonFootprint?: number | string; + latency?: 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 removeLastEvent(): void { + this.crossChainEvents.pop(); + this.lastUpdateDate = new Date(); + } + + public getCrossChainLogAttributes(): string[] { + return [ + "caseID", + "receiptID", + "timestamp", + "blockchainID", + "invocationType", + "methodName", + "parameters", + "identity", + "cost", + "carbonFootprint", + "latency", + ]; + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/crosschain-model.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/crosschain-model.ts new file mode 100644 index 0000000000..ad601daafe --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/crosschain-model.ts @@ -0,0 +1,191 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; +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; + private crossChainState: Map< + string, + [AssetState | undefined, AssetState | undefined] + >; + private sourceLedgerMethods: string[] = []; + private targetLedgerMethods: string[] = []; + + constructor() { + this.id = uuidv4(); + this.crossChainTransactions = new Map< + string, + CrossChainTransactionSchema + >(); + this.lastAggregationDate = new Date(); + this.crossChainState = new Map< + string, + [AssetState | undefined, AssetState | undefined] + >(); + } + + get lastAggregation(): Date { + return this.lastAggregationDate; + } + + get ccModelType(): CrossChainModelType | undefined { + return this.modelType; + } + + public setType(modelType: CrossChainModelType): void { + this.modelType = modelType; + } + + 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); + } + + public setSourceLedgerMethod(methodName: string): void { + this.sourceLedgerMethods.push(methodName); + } + + public sourceLedgerIncludesMethod(methodName: string): boolean { + return this.sourceLedgerMethods.includes(methodName); + } + + public setTargetLedgerMethod(methodName: string): void { + this.targetLedgerMethods.push(methodName); + } + + public targetLedgerIncludesMethod(methodName: string): boolean { + return this.targetLedgerMethods.includes(methodName); + } + + public setAssetStateSourceLedger(ccTxID: string, details: AssetState): void { + const prevState = this.crossChainState.get(ccTxID); + if (!prevState) { + this.crossChainState.set(ccTxID, [details, undefined]); + return; + } + this.crossChainState.set(ccTxID, [details, prevState[1]]); + } + + public setAssetStateTargetLedger(ccTxID: string, details: AssetState): void { + const prevState = this.crossChainState.get(ccTxID); + if (!prevState) { + this.crossChainState.set(ccTxID, [undefined, details]); + return; + } + this.crossChainState.set(ccTxID, [prevState[0], details]); + } + + public getCrossChainState(): string | undefined { + let ccState: string = ""; + for (const [ + ccTxID, + [assetStateSource, assetStateTarget], + ] of this.crossChainState.entries()) { + let txData: string; + if (assetStateSource && assetStateTarget) { + txData = + ccTxID + + "\n" + + assetStateSource.assetID + + ";" + + assetStateSource.assetState + + ";" + + assetStateSource.ledger + + ";" + + assetStateSource.lastStateUpdate + + "\n" + + assetStateTarget.assetID + + ";" + + assetStateTarget.assetState + + ";" + + assetStateTarget.ledger + + ";" + + assetStateTarget.lastStateUpdate + + "\n"; + } else if (assetStateSource) { + txData = + ccTxID + + "\n" + + assetStateSource.assetID + + ";" + + assetStateSource.assetState + + ";" + + assetStateSource.ledger + + ";" + + assetStateSource.lastStateUpdate + + "\n"; + } else if (assetStateTarget) { + txData = + ccTxID + + "\n" + + assetStateTarget.assetID + + ";" + + assetStateTarget.assetState + + ";" + + assetStateTarget.ledger + + ";" + + assetStateTarget.lastStateUpdate + + "\n"; + } else { + continue; + } + ccState = ccState + txData + "\n"; + } + return ccState; + } +} + +export enum CrossChainModelType { + PetriNet, + ProcessTree, +} + +export type CrossChainTransactionSchema = { + // the IDs of all cross chain events of the cross chain transaction + processedCrossChainEvents: string[]; + latency: number; + carbonFootprint: number | undefined; + cost: number | undefined; + throughput: number; + latestUpdate: Date; +}; + +export type AssetState = { + assetID: string; + assetState: string; + ledger: LedgerType; + lastStateUpdate: Date; +}; diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/transaction-receipt.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/transaction-receipt.ts new file mode 100644 index 0000000000..96475d1169 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/transaction-receipt.ts @@ -0,0 +1,33 @@ +import { LedgerType } from "@hyperledger/cactus-core-api"; +import { FabricSigningCredential } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; + +export interface TransactionReceipt { + caseID: string; + blockchainID: LedgerType; + invocationType: string; + methodName: string; + parameters: string[]; + timestamp: Date; +} + +export interface FabricV2TxReceipt extends TransactionReceipt { + channelName: string; + transactionID: string; + contractName: string; + signingCredentials: FabricSigningCredential; + cost?: number; +} + +export interface BesuV2TxReceipt extends TransactionReceipt { + transactionID: string; + gasUsed: number; + from: string; + gasPrice?: number; +} + +export interface EthereumTxReceipt extends TransactionReceipt { + transactionID: string; + gasUsed: number | string; + from: string; + effectiveGasPrice?: string; +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/utils.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/utils.ts new file mode 100644 index 0000000000..df20eae7a1 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/models/utils.ts @@ -0,0 +1,7 @@ +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-ccmodel-hephaestus/src/main/typescript/plugin-ccmodel-hephaestus.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/plugin-ccmodel-hephaestus.ts new file mode 100644 index 0000000000..d9430b4192 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/plugin-ccmodel-hephaestus.ts @@ -0,0 +1,844 @@ +import { + IPluginWebService, + IWebServiceEndpoint, + ICactusPlugin, + ICactusPluginOptions, + LedgerType, +} from "@hyperledger/cactus-core-api"; + +import { RuntimeError } from "run-time-error-cjs"; + +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 { + calculateGasPriceEth, + CarbonFootPrintConstants, +} from "./models/carbon-footprint"; +import { + CrossChainEvent, + CrossChainEventLog, +} from "./models/cross-chain-event"; +import { createModelPM4PY, checkConformancePM4PY } from "./ccmodel-adapter"; + +export interface IWebAppOptions { + port: number; + hostname: string; +} +import { + CrossChainModel, + CrossChainModelType, + CrossChainTransactionSchema, + AssetState, +} from "./models/crosschain-model"; +import { + BesuV2TxReceipt, + EthereumTxReceipt, + FabricV2TxReceipt, +} from "./models/transaction-receipt"; +import { millisecondsLatency } from "./models/utils"; +import { RunTransactionV1Exchange as RunTransactionV1ExchangeBesu } from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { RunTransactionV1Exchange as RunTransactionV1ExchangeEth } from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { RunTxReqWithTxId } from "@hyperledger/cactus-plugin-ledger-connector-fabric"; + +import { Observable } from "rxjs"; +import { filter, tap } from "rxjs/operators"; + +export interface IPluginCcModelHephaestusOptions extends ICactusPluginOptions { + connectorRegistry?: PluginRegistry; + logLevel?: LogLevelDesc; + webAppOptions?: IWebAppOptions; + instanceId: string; + ethTxObservable?: Observable; + besuTxObservable?: Observable; + fabricTxObservable?: Observable; + sourceLedger: LedgerType; + targetLedger: LedgerType; + ccLogsDir?: string; +} + +export class CcModelHephaestus implements ICactusPlugin, IPluginWebService { + private readonly log: Logger; + private readonly instanceId: string; + private endpoints: IWebServiceEndpoint[] | undefined; + private crossChainLog: CrossChainEventLog; + private unmodeledEventLog: CrossChainEventLog; + private nonConformedCrossChainLog: CrossChainEventLog; + private readonly nonConformedCCTxs: string[]; + private crossChainModel: CrossChainModel; + public readonly className = "plugin-ccmodel-hephaestus"; + private caseID: string; + private readonly besuTxObservable?: Observable; + private readonly ethTxObservable?: Observable; + private readonly fabricTxObservable?: Observable; + private readonly sourceLedger: LedgerType; + private readonly targetLedger: LedgerType; + private startMonitoring: number | null = null; + private isModeling: boolean; + private readonly ccLogsDir: string; + + constructor(public readonly options: IPluginCcModelHephaestusOptions) { + const startTime = new Date(); + const fnTag = `PluginCcModelHephaestus#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.log = LoggerProvider.getOrCreate({ + label: label, + level: level, + }); + this.instanceId = this.options.instanceId; + this.crossChainLog = new CrossChainEventLog({ + name: "HEPHAESTUS_EVENT_LOGS", + }); + this.unmodeledEventLog = new CrossChainEventLog({ + name: "HEPHAESTUS_UNMODELED_LOGS", + }); + this.nonConformedCrossChainLog = new CrossChainEventLog({ + name: "HEPHAESTUS_NON_CONFORMANCE_LOGS", + }); + + this.caseID = "UNDEFINED_CASE_ID"; + + this.ethTxObservable = options.ethTxObservable; + this.besuTxObservable = options.besuTxObservable; + this.fabricTxObservable = options.fabricTxObservable; + + this.sourceLedger = options.sourceLedger; + this.targetLedger = options.targetLedger; + + //todo should allow different models to be instantiated + this.crossChainModel = new CrossChainModel(); + + this.isModeling = true; + + this.nonConformedCCTxs = []; + + this.ccLogsDir = + options.ccLogsDir || path.join(__dirname, "..", "..", "test", "ccLogs"); + // Create directories if they don't exist + if (!fs.existsSync(this.ccLogsDir)) { + fs.mkdirSync(path.join(this.ccLogsDir, "csv"), { recursive: true }); + fs.mkdirSync(path.join(this.ccLogsDir, "json"), { recursive: true }); + } + + 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 ccModel(): CrossChainModel { + return this.crossChainModel; + } + + get numberEventsLog(): number { + return this.crossChainLog.numberEvents(); + } + + get numberEventsUnmodeledLog(): number { + return this.unmodeledEventLog.numberEvents(); + } + + public purgeNonConformedEvents(): void { + this.nonConformedCrossChainLog.purgeLogs(); + } + + get numberEventsNonConformedLog(): number { + return this.nonConformedCrossChainLog.numberEvents(); + } + + public purgeCrossChainEvents(): void { + this.crossChainLog.purgeLogs(); + } + + public getInstanceId(): string { + return this.instanceId; + } + + public getCaseId(): string { + return this.caseID; + } + + public setIsModeling(bool: boolean): void { + this.isModeling = bool; + } + + public setCaseId(id: string): void { + this.unmodeledEventLog.purgeLogs(); + this.caseID = id; + } + + public async onPluginInit(): Promise { + return; + } + + public async shutdown(): Promise { + this.log.info(`Shutting down...`); + } + + 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 getPackageName(): string { + return `@hyperledger/cactus-plugin-ccmodel-hephaestus`; + } + + private createReceiptFromRunTransactionV1ExchangeBesu( + data: RunTransactionV1ExchangeBesu, + ): BesuV2TxReceipt { + return { + caseID: this.caseID, + blockchainID: LedgerType.Besu2X, + timestamp: data.timestamp, + transactionID: data.response.transactionReceipt.transactionHash, + from: data.response.transactionReceipt.from, + invocationType: data.request.invocationType, + methodName: data.request.methodName, + parameters: data.request.params, + gasUsed: data.response.transactionReceipt.gasUsed, + gasPrice: data.request.gasPrice as number, + }; + } + + private createReceiptFromRunTransactionV1ExchangeEth( + data: RunTransactionV1ExchangeEth, + ): EthereumTxReceipt { + return { + caseID: this.caseID, + blockchainID: LedgerType.Ethereum, + timestamp: data.timestamp, + transactionID: data.response.transactionReceipt.transactionHash, + from: data.response.transactionReceipt.from, + invocationType: data.request.invocationType, + methodName: data.request.methodName, + parameters: data.request.params, + gasUsed: data.response.transactionReceipt.gasUsed, + effectiveGasPrice: data.response.transactionReceipt.effectiveGasPrice, + }; + } + + private createReceiptFromRunTxReqWithTxId( + data: RunTxReqWithTxId, + ): FabricV2TxReceipt { + return { + caseID: this.caseID, + blockchainID: LedgerType.Fabric2, + timestamp: data.timestamp, + channelName: data.request.channelName, + transactionID: data.transactionId, + contractName: data.request.contractName, + signingCredentials: data.request.signingCredential, + invocationType: data.request.invocationType, + methodName: data.request.methodName, + parameters: data.request.params, + }; + } + + private watchRunTransactionV1ExchangeBesu(duration: number = 0): void { + const fnTag = `${this.className}#watchRunTransactionV1ExchangeBesu()`; + this.log.debug(fnTag); + + if (!this.besuTxObservable) { + this.log.debug( + `${fnTag}-No Besu transaction observable provided, monitoring skipped`, + ); + return; + } + + this.besuTxObservable + .pipe( + // Filter only the values emitted within the specified duration + // if no duration provided, skip filtering + duration >= 0 + ? filter( + (data) => + this.startMonitoring! - data.timestamp.getTime() <= duration, + ) + : tap(), + ) + .subscribe({ + next: async (data: RunTransactionV1ExchangeBesu) => { + // Handle the data whenever a new value is received by the observer: + // this includes creating the receipt, then the cross-chain event + // and check its conformance to the model, if the model is already defined + const receipt = + this.createReceiptFromRunTransactionV1ExchangeBesu(data); + const ccEvent = this.createCrossChainEventFromBesuReceipt( + receipt, + this.isModeling, + ); + const model = this.ccModel.getModel(CrossChainModelType.PetriNet); + + if (!this.isModeling && model && this.numberEventsUnmodeledLog != 0) { + this.updateCcStateAndCheckConformance(ccEvent, model); + } + }, + error: (error: unknown) => { + this.log.error( + `${fnTag}- error`, + error, + `receiving RunTransactionV1ExchangeBesu by Besu transaction observable`, + this.besuTxObservable, + ); + throw error; + }, + }); + } + + private watchRunTransactionV1ExchangeEth(duration: number = 0): void { + const fnTag = `${this.className}#watchRunTransactionV1ExchangeEth()`; + this.log.debug(fnTag); + + if (!this.ethTxObservable) { + this.log.debug( + `${fnTag}-No Ethereum transaction observable provided, monitoring skipped`, + ); + return; + } + + this.ethTxObservable + .pipe( + // Filter only the values emitted within the specified duration + // if no duration provided, skip filtering + duration >= 0 + ? filter( + (data) => + this.startMonitoring! - data.timestamp.getTime() <= duration, + ) + : tap(), + ) + .subscribe({ + next: async (data: RunTransactionV1ExchangeEth) => { + // Handle the data whenever a new value is received by the observer + // this includes creating the receipt, then the cross-chain event + // and check its conformance to the model, if the model is already defined + const receipt = + this.createReceiptFromRunTransactionV1ExchangeEth(data); + const ccEvent = this.createCrossChainEventFromEthReceipt( + receipt, + this.isModeling, + ); + const model = this.ccModel.getModel(CrossChainModelType.PetriNet); + if (!this.isModeling && model && this.numberEventsUnmodeledLog != 0) { + this.updateCcStateAndCheckConformance(ccEvent, model); + } + }, + error: (error: unknown) => { + this.log.error( + `${fnTag}- error`, + error, + `receiving RunTransactionV1ExchangeEth by Ethereum transaction observable`, + this.ethTxObservable, + ); + throw error; + }, + }); + } + + private watchRunTxReqWithTxId(duration: number = 0): void { + const fnTag = `${this.className}#watchRunTxReqWithTxId()`; + this.log.debug(fnTag); + + if (!this.fabricTxObservable) { + this.log.debug( + `${fnTag}-No Fabric transaction observable provided, monitoring skipped`, + ); + return; + } + + this.fabricTxObservable + .pipe( + // Filter only the values emitted within the specified duration + // if no duration provided, skip filtering + duration >= 0 + ? filter( + (data) => + this.startMonitoring! - data.timestamp.getTime() <= duration, + ) + : tap(), + ) + .subscribe({ + next: async (data: RunTxReqWithTxId) => { + // Handle the data whenever a new value is received by the observer + // this includes creating the receipt, then the cross-chain event + // and check its conformance to the model, if the model is already defined + const receipt = this.createReceiptFromRunTxReqWithTxId(data); + const ccEvent = this.createCrossChainEventFromFabricReceipt( + receipt, + this.isModeling, + ); + const model = this.ccModel.getModel(CrossChainModelType.PetriNet); + if (!this.isModeling && model && this.numberEventsUnmodeledLog != 0) { + this.updateCcStateAndCheckConformance(ccEvent, model); + } + }, + error: (error: unknown) => { + this.log.error( + `${fnTag}- error`, + error, + `receiving RunTxReqWithTxId by Fabric transaction observable`, + this.fabricTxObservable, + ); + throw error; + }, + }); + } + + public monitorTransactions(duration: number = -1): void { + const fnTag = `${this.className}#monitorTransactions()`; + this.log.debug(fnTag); + + this.startMonitoring = Date.now(); + this.watchRunTransactionV1ExchangeBesu(duration); + this.watchRunTransactionV1ExchangeEth(duration); + this.watchRunTxReqWithTxId(duration); + return; + } + + private async updateCcStateAndCheckConformance( + ccEvent: CrossChainEvent, + model: string, + ): Promise { + const assetState: AssetState = { + assetID: ccEvent.parameters[0], + assetState: ccEvent.methodName, + ledger: ccEvent.blockchainID, + lastStateUpdate: new Date(), + }; + const ledgerHasMethod = this.addAssetToCcState(ccEvent, assetState); + await this.checkConformance(model, ledgerHasMethod); + } + + private addAssetToCcState( + ccEvent: CrossChainEvent, + assetState: AssetState, + ): boolean { + if ( + this.sourceLedger == ccEvent.blockchainID && + this.ccModel.sourceLedgerIncludesMethod(ccEvent.methodName) + ) { + this.ccModel.setAssetStateSourceLedger(this.caseID, assetState); + return true; + } else if ( + this.targetLedger == ccEvent.blockchainID && + this.ccModel.targetLedgerIncludesMethod(ccEvent.methodName) + ) { + this.ccModel.setAssetStateTargetLedger(this.caseID, assetState); + return true; + } + return false; + } + + private createCrossChainEventFromBesuReceipt( + besuReceipt: BesuV2TxReceipt, + updatingCCModel: boolean, + ): CrossChainEvent { + const ccEventFromBesu: CrossChainEvent = { + caseID: besuReceipt.caseID, + receiptID: besuReceipt.transactionID, + blockchainID: besuReceipt.blockchainID, + invocationType: besuReceipt.invocationType, + methodName: besuReceipt.methodName, + parameters: besuReceipt.parameters, + timestamp: besuReceipt.timestamp.toISOString(), + identity: besuReceipt.from, + cost: besuReceipt.gasUsed, + carbonFootprint: CarbonFootPrintConstants(besuReceipt.blockchainID), + latency: millisecondsLatency(besuReceipt.timestamp), + }; + + if (this.isModeling == false && updatingCCModel == false) { + this.unmodeledEventLog.addCrossChainEvent(ccEventFromBesu); + + this.log.info( + "Added Cross Chain event from BESU for conformance checking", + ); + this.log.debug( + `Conformance Cross-chain log: ${JSON.stringify(ccEventFromBesu)}`, + ); + } else { + this.crossChainLog.addCrossChainEvent(ccEventFromBesu); + this.log.info("Added Cross Chain event from BESU"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventFromBesu)}`); + } + return ccEventFromBesu; + } + + private createCrossChainEventFromEthReceipt( + ethReceipt: EthereumTxReceipt, + updatingCCModel: boolean, + ): CrossChainEvent { + const ccEventFromEth: CrossChainEvent = { + caseID: ethReceipt.caseID, + receiptID: ethReceipt.transactionID, + blockchainID: ethReceipt.blockchainID, + invocationType: ethReceipt.invocationType, + methodName: ethReceipt.methodName, + parameters: ethReceipt.parameters, + timestamp: ethReceipt.timestamp.toISOString(), + identity: ethReceipt.from, + cost: calculateGasPriceEth( + ethReceipt.gasUsed as number, + ethReceipt.effectiveGasPrice, + ), + carbonFootprint: CarbonFootPrintConstants(ethReceipt.blockchainID), + latency: millisecondsLatency(ethReceipt.timestamp), + }; + + if (this.isModeling == false && updatingCCModel == false) { + this.unmodeledEventLog.addCrossChainEvent(ccEventFromEth); + + this.log.info( + "Added Cross Chain event from ETHEREUM for conformance checking", + ); + this.log.debug( + `Conformance Cross-chain log: ${JSON.stringify(ccEventFromEth)}`, + ); + } else { + this.crossChainLog.addCrossChainEvent(ccEventFromEth); + this.log.info("Added Cross Chain event from ETHEREUM"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventFromEth)}`); + } + return ccEventFromEth; + } + + private createCrossChainEventFromFabricReceipt( + fabricReceipt: FabricV2TxReceipt, + updatingCCModel: boolean, + ): CrossChainEvent { + const ccEventFromFabric: CrossChainEvent = { + caseID: fabricReceipt.caseID, + receiptID: fabricReceipt.transactionID, + blockchainID: fabricReceipt.blockchainID, + invocationType: fabricReceipt.invocationType, + methodName: fabricReceipt.methodName, + parameters: fabricReceipt.parameters, + timestamp: fabricReceipt.timestamp.toISOString(), + identity: fabricReceipt.signingCredentials.keychainRef, + cost: fabricReceipt.cost || 0, + carbonFootprint: CarbonFootPrintConstants(fabricReceipt.blockchainID), + latency: millisecondsLatency(fabricReceipt.timestamp), + }; + + if (this.isModeling == false && updatingCCModel == false) { + this.unmodeledEventLog.addCrossChainEvent(ccEventFromFabric); + + this.log.info( + "Added Cross Chain event from FABRIC for conformance checking", + ); + this.log.debug( + `Conformance Cross-chain log: ${JSON.stringify(ccEventFromFabric)}`, + ); + } else { + this.crossChainLog.addCrossChainEvent(ccEventFromFabric); + this.log.info("Added Cross Chain event from FABRIC"); + this.log.debug(`Cross-chain log: ${JSON.stringify(ccEventFromFabric)}`); + } + return ccEventFromFabric; + } + + // Parses the cross chain event log to update 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 + private 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 = { + processedCrossChainEvents: [], + latency: 0, + carbonFootprint: 0, + cost: 0, + throughput: 0, + latestUpdate: newAggregationDate, + }; + const lastAggregatedTime = new Date(lastAggregated).getTime(); + console.log(logEntries); + const logsToAggregate = logEntries.filter( + (log) => new Date(log.timestamp).getTime() > lastAggregatedTime, + ); + console.log(logsToAggregate); + + 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 ccTxID = eventEntry.caseID; + const eventID = eventEntry.receiptID; + const latency = (eventEntry.latency as number) || 0; + const carbonFootprint = (eventEntry.carbonFootprint as number) || 0; + const cost = (eventEntry.cost as number) || 0; + + if (ccTxSet?.has(ccTxID)) { + const existingCCTx = ccTxSet.get(ccTxID); + 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 updatedMetrics = { + 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, + }; + this.crossChainModel.setCCTxs(ccTxID, updatedMetrics); + } else { + metrics = { + processedCrossChainEvents: [eventID], + latency: latency, + carbonFootprint: carbonFootprint, + cost: cost, + throughput: Number( + (latency != 0 ? 1 / latency : 0).toFixed(3) as unknown as number, + ), + latestUpdate: lastAggregated, + }; + this.crossChainModel.setCCTxs(ccTxID, 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() as (keyof CrossChainEvent)[]; + const logName = name + ? `${name}.csv` + : `hephaestus_log_${startTime.getTime()}.csv`; + const csvFolder = path.join(this.ccLogsDir, "csv"); + const logPath = path.join(csvFolder, logName); + const fnTag = `${this.className}#persistCrossChainLogCsv()`; + const ccEvents = this.crossChainLog.logEntries; + + try { + // Convert log entries to CSV rows + const csvRows = ccEvents.map((entry) => { + return columns + .map((header) => { + const value = entry[header]; + return typeof value === "string" && value.includes(";") + ? `"${value}"` + : value; + }) + .join(";"); + }); + + // Concatenate columns and rows into a single CSV string + const data = [columns.join(";"), ...csvRows].join("\n"); + this.log.debug(data); + fs.writeFileSync(logPath, data); + + const finalTime = new Date(); + this.log.debug( + `EVAL-${this.className}-PERSIST-LOG-CVS:${finalTime.getTime() - startTime.getTime()}`, + ); + return logPath; + } catch (error) { + const errorMessage = `${fnTag} Failed to export cross-chain event log to CSV file:`; + throw new RuntimeError(errorMessage, error); + } + } + + public async persistCrossChainLogJson(name?: string): Promise { + const startTime = new Date(); + const logName = name + ? `${name}.json` + : `hephaestus_log_${startTime.getTime()}.json`; + const jsonFolder = path.join(this.ccLogsDir, "json"); + const logPath = path.join(jsonFolder, logName); + const fnTag = `${this.className}#persistCrossChainLogJson()`; + + const ccEvents = this.crossChainLog.logEntries; + + try { + const data = JSON.stringify(ccEvents, null, 2); + this.log.debug(data); + fs.writeFileSync(logPath, data); + + const finalTime = new Date(); + this.log.debug( + `EVAL-${this.className}-PERSIST-LOG-JSON:${finalTime.getTime() - startTime.getTime()}`, + ); + return logPath; + } catch (error) { + const errorMessage = `${fnTag} Failed to export cross-chain event log to JSON file:`; + throw new RuntimeError(errorMessage, error); + } + } + + private async persistUnmodeledEventLog(): Promise { + const startTime = new Date(); + const logName = `hephaestus_log_${startTime.getTime()}`; + const jsonFolder = path.join(this.ccLogsDir, "json"); + const logPath = path.join(jsonFolder, logName + ".json"); + const fnTag = `${this.className}#persistUnmodeledEventLog()`; + + const ccLogEvents = this.unmodeledEventLog.logEntries; + + try { + const data = JSON.stringify(ccLogEvents, null, 2); + this.log.debug(data); + fs.writeFileSync(logPath, data); + + const finalTime = new Date(); + this.log.debug( + `EVAL-${this.className}-PERSIST-LOG-JSON:${finalTime.getTime() - startTime.getTime()}`, + ); + return logPath; + } catch (error) { + const errorMessage = `${fnTag} Failed to export cross-chain event log to JSON file:`; + throw new RuntimeError(errorMessage, error); + } + } + + // Receives a serialized model and saves it + public saveModel(modelType: CrossChainModelType, model: string): void { + this.crossChainModel.saveModel(modelType, model); + } + + // Gets the saved serialized model with the specified CrossChainModelType + public getModel(modelType: CrossChainModelType): string | undefined { + return this.crossChainModel.getModel(modelType); + } + + public setLedgerMethods(): void { + const logEntries = this.crossChainLog.logEntries; + logEntries.forEach((event) => { + if (this.sourceLedger == event.blockchainID) { + this.ccModel.setSourceLedgerMethod(event.methodName); + } + if (this.targetLedger == event.blockchainID) { + this.ccModel.setTargetLedgerMethod(event.methodName); + } + }); + } + + public async createModel(): Promise { + const logPath = await this.persistCrossChainLogJson(); + await this.aggregateCcTx(); + const petriNet = createModelPM4PY(logPath); + this.ccModel.setType(CrossChainModelType.PetriNet); + this.saveModel(CrossChainModelType.PetriNet, petriNet); + this.setLedgerMethods(); + return petriNet; + } + + // creates a file with unmodeled logs and performs a conformance check + private async checkConformance( + serializedCCModel: string, + ledgerHasMethod: boolean, + ): Promise { + const logPath = await this.persistUnmodeledEventLog(); + const conformanceDetails = checkConformancePM4PY( + logPath, + serializedCCModel, + ); + return this.filterLogsByConformance(conformanceDetails, ledgerHasMethod); + } + + private filterLogsByConformance( + conformanceDetails: string | undefined, + ledgerHasMethod: boolean, + ): string { + const fnTag = `${this.className}#filterLogsByConformance()`; + if (!conformanceDetails) { + throw new Error(`${fnTag} conformance details falsy.`); + } + + const details = conformanceDetails.split("\n"); + const diagnosis = details[0]; + + if ( + diagnosis.includes("NON-CONFORMANCE") || + diagnosis.includes("SKIPPED ACTIVITY") || + !ledgerHasMethod + ) { + this.nonConformedCCTxs.push(this.caseID); + this.unmodeledEventLog.logEntries.forEach((event) => { + this.nonConformedCrossChainLog.addCrossChainEvent(event); + }); + this.unmodeledEventLog.purgeLogs(); + } else if (diagnosis.includes("PARTIAL CONFORMANCE")) { + if (this.nonConformedCCTxs.includes(this.caseID)) { + this.unmodeledEventLog.logEntries.forEach((event) => { + this.nonConformedCrossChainLog.addCrossChainEvent(event); + }); + this.unmodeledEventLog.purgeLogs(); + } + } else if (diagnosis.includes("FULL CONFORMANCE")) { + this.unmodeledEventLog.logEntries.forEach((event) => { + this.crossChainLog.addCrossChainEvent(event); + }); + this.unmodeledEventLog.purgeLogs(); + this.createModel(); + } + console.log(details); + return diagnosis; + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/plugin-factory-ccmodel-hephaestus.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/plugin-factory-ccmodel-hephaestus.ts new file mode 100644 index 0000000000..37676d287b --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/plugin-factory-ccmodel-hephaestus.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginCcModelHephaestusOptions, + CcModelHephaestus, +} from "./plugin-ccmodel-hephaestus"; + +export class PluginFactoryWebService extends PluginFactory< + CcModelHephaestus, + IPluginCcModelHephaestusOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginCcModelHephaestusOptions, + ): Promise { + return new CcModelHephaestus(pluginOptions); + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/public-api.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..3e81fe76c1 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/main/typescript/public-api.ts @@ -0,0 +1,16 @@ +export { + CcModelHephaestus, + IPluginCcModelHephaestusOptions, + IWebAppOptions, +} from "./plugin-ccmodel-hephaestus"; + +export { PluginFactoryWebService } from "./plugin-factory-ccmodel-hephaestus"; + +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +import { PluginFactoryWebService } from "./plugin-factory-ccmodel-hephaestus"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryWebService(pluginFactoryOptions); +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/LockAsset.json b/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/LockAsset.json new file mode 100644 index 0000000000..fda660818a --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/LockAsset.json @@ -0,0 +1,2504 @@ +{ + "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": "isAssetLocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "id", + "type": "string" + } + ], + "name": "isPresent", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "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.15+commit.e14f2714\"},\"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\":\"isAssetLocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"id\",\"type\":\"string\"}],\"name\":\"isPresent\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"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\":{\"/home/andre_9a/cactus/packages/cactus-plugin-satp-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol\":\"LockAsset\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/andre_9a/cactus/packages/cactus-plugin-satp-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol\":{\"keccak256\":\"0xde0438e010cd77bfc1516697230fbcec20e85bd7fdbc8df9225abb5c9dd6c7ed\",\"urls\":[\"bzz-raw://865f301eada0615be375c9313d3abce9458dcbb5311dedf805cad921accda557\",\"dweb:/ipfs/QmY1r6Z7PM8ypYy59rWqozy3Mkcv9nMd4xRcfpCbwN4LQK\"]}},\"version\":1}", + "bytecode": "608060405234801561001057600080fd5b506105f9806100206000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063cd5286d01161005b578063cd5286d0146100d2578063db9cc41014610112578063def60e0d14610125578063e24aa37c1461013857600080fd5b80631ae4eb68146100825780635e82d0a6146100aa578063bc548275146100bf575b600080fd5b610095610090366004610525565b61014b565b60405190151581526020015b60405180910390f35b6100bd6100b8366004610525565b6101b5565b005b6100956100cd366004610525565b61022a565b6100e56100e0366004610525565b610259565b6040805182516001600160a01b0316815260208084015115159082015291810151908201526060016100a1565b6100bd610120366004610567565b6102d1565b6100bd610133366004610525565b6103bc565b6100bd610146366004610525565b610402565b600080600184846040516101609291906105b3565b9081526040519081900360200190205460ff1690508061017f57600080fd5b600084846040516101919291906105b3565b9081526040519081900360200190205460ff600160a01b9091041691505092915050565b6000600183836040516101c99291906105b3565b9081526040519081900360200190205460ff169050806101e857600080fd5b6001600084846040516101fc9291906105b3565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60006001838360405161023e9291906105b3565b9081526040519081900360200190205460ff16905092915050565b6040805160608101825260008082526020820181905291810191909152600083836040516102889291906105b3565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116102de57600080fd5b80600084846040516102f19291906105b3565b90815260200160405180910390206001018190555033600084846040516103199291906105b3565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155600090819061035b90869086906105b3565b9081526040519081900360200181208054921515600160a01b0260ff60a01b1990931692909217909155600190819061039790869086906105b3565b908152604051908190036020019020805491151560ff19909216919091179055505050565b6000600183836040516103d09291906105b3565b9081526040519081900360200190205460ff169050806103ef57600080fd5b60008084846040516101fc9291906105b3565b6000600183836040516104169291906105b3565b9081526040519081900360200190205460ff1690508061043557600080fd5b60008084846040516104489291906105b3565b9081526040519081900360200190205460ff600160a01b9091041690508061046f57600080fd5b600084846040516104819291906105b3565b90815260405190819003602001812080546001600160a81b031916815560006001918201819055916104b690879087906105b3565b908152604051908190036020019020805491151560ff1990921691909117905550505050565b60008083601f8401126104ee57600080fd5b50813567ffffffffffffffff81111561050657600080fd5b60208301915083602082850101111561051e57600080fd5b9250929050565b6000806020838503121561053857600080fd5b823567ffffffffffffffff81111561054f57600080fd5b61055b858286016104dc565b90969095509350505050565b60008060006040848603121561057c57600080fd5b833567ffffffffffffffff81111561059357600080fd5b61059f868287016104dc565b909790965060209590950135949350505050565b818382376000910190815291905056fea2646970667358221220256d00c6b6e97111d048c9a6a3069f078880240932a634dae7aec643dc6ade7664736f6c634300080f0033", + "deployedBytecode": "608060405234801561001057600080fd5b506004361061007d5760003560e01c8063cd5286d01161005b578063cd5286d0146100d2578063db9cc41014610112578063def60e0d14610125578063e24aa37c1461013857600080fd5b80631ae4eb68146100825780635e82d0a6146100aa578063bc548275146100bf575b600080fd5b610095610090366004610525565b61014b565b60405190151581526020015b60405180910390f35b6100bd6100b8366004610525565b6101b5565b005b6100956100cd366004610525565b61022a565b6100e56100e0366004610525565b610259565b6040805182516001600160a01b0316815260208084015115159082015291810151908201526060016100a1565b6100bd610120366004610567565b6102d1565b6100bd610133366004610525565b6103bc565b6100bd610146366004610525565b610402565b600080600184846040516101609291906105b3565b9081526040519081900360200190205460ff1690508061017f57600080fd5b600084846040516101919291906105b3565b9081526040519081900360200190205460ff600160a01b9091041691505092915050565b6000600183836040516101c99291906105b3565b9081526040519081900360200190205460ff169050806101e857600080fd5b6001600084846040516101fc9291906105b3565b9081526040519081900360200190208054911515600160a01b0260ff60a01b19909216919091179055505050565b60006001838360405161023e9291906105b3565b9081526040519081900360200190205460ff16905092915050565b6040805160608101825260008082526020820181905291810191909152600083836040516102889291906105b3565b908152604080516020928190038301812060608201835280546001600160a01b0381168352600160a01b900460ff16151593820193909352600190920154908201529392505050565b600081116102de57600080fd5b80600084846040516102f19291906105b3565b90815260200160405180910390206001018190555033600084846040516103199291906105b3565b90815260405190819003602001812080546001600160a01b03939093166001600160a01b031990931692909217909155600090819061035b90869086906105b3565b9081526040519081900360200181208054921515600160a01b0260ff60a01b1990931692909217909155600190819061039790869086906105b3565b908152604051908190036020019020805491151560ff19909216919091179055505050565b6000600183836040516103d09291906105b3565b9081526040519081900360200190205460ff169050806103ef57600080fd5b60008084846040516101fc9291906105b3565b6000600183836040516104169291906105b3565b9081526040519081900360200190205460ff1690508061043557600080fd5b60008084846040516104489291906105b3565b9081526040519081900360200190205460ff600160a01b9091041690508061046f57600080fd5b600084846040516104819291906105b3565b90815260405190819003602001812080546001600160a81b031916815560006001918201819055916104b690879087906105b3565b908152604051908190036020019020805491151560ff1990921691909117905550505050565b60008083601f8401126104ee57600080fd5b50813567ffffffffffffffff81111561050657600080fd5b60208301915083602082850101111561051e57600080fd5b9250929050565b6000806020838503121561053857600080fd5b823567ffffffffffffffff81111561054f57600080fd5b61055b858286016104dc565b90969095509350505050565b60008060006040848603121561057c57600080fd5b833567ffffffffffffffff81111561059357600080fd5b61059f868287016104dc565b909790965060209590950135949350505050565b818382376000910190815291905056fea2646970667358221220256d00c6b6e97111d048c9a6a3069f078880240932a634dae7aec643dc6ade7664736f6c634300080f0033", + "sourceMap": "543:1460:0:-:0;;;;;;;;;;;;;;;;;;;", + "deployedSourceMap": "543:1460:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1770:231;;;;;;:::i;:::-;;:::i;:::-;;;948:14:1;;941:22;923:41;;911:2;896:18;1770:231:0;;;;;;;;1011:144;;;;;;:::i;:::-;;:::i;:::-;;1665:101;;;;;;:::i;:::-;;:::i;865:103::-;;;;;;:::i;:::-;;:::i;:::-;;;;1183:13:1;;-1:-1:-1;;;;;1179:39:1;1161:58;;1289:4;1277:17;;;1271:24;1264:32;1257:40;1235:20;;;1228:70;1342:17;;;1336:24;1314:20;;;1307:54;1149:2;1134:18;865:103:0;975:392:1;643:218:0;;;;;;:::i;:::-;;:::i;1200:147::-;;;;;;:::i;:::-;;:::i;1351:310::-;;;;;;:::i;:::-;;:::i;1770:231::-;1834:4;1848:11;1862;1874:2;;1862:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1862:15:0;1885;;;;;;1979:6;1986:2;;1979:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:17;;-1:-1:-1;;;1979:17:0;;;;;-1:-1:-1;;1770:231:0;;;;:::o;1011:144::-;1065:11;1079;1091:2;;1079:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1079:15:0;1102;;;;;;1146:4;1126:6;1133:2;;1126:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:24;;;;;-1:-1:-1;;;1126:24:0;-1:-1:-1;;;;1126:24:0;;;;;;;;;-1:-1:-1;;;1011:144:0:o;1665:101::-;1725:4;1746:11;1758:2;;1746:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1665:101:0;;;;:::o;865:103::-;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;;953:6:0;960:2;;953:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;946:17;;;;;;;-1:-1:-1;;;;;946:17:0;;;;-1:-1:-1;;;946:17:0;;;;;;;;;;;;;;;;;;;;;;953:10;865:103;-1:-1:-1;;;865:103:0:o;643:218::-;723:1;718:4;:6;710:15;;;;;;750:4;733:6;740:2;;733:10;;;;;;;:::i;:::-;;;;;;;;;;;;;:15;;:21;;;;783:10;762:6;769:2;;762:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:31;;-1:-1:-1;;;;;762:31:0;;;;-1:-1:-1;;;;;;762:31:0;;;;;;;;;;:18;;;;801:10;;808:2;;;;801:10;:::i;:::-;;;;;;;;;;;;;;:25;;;;;-1:-1:-1;;;801:25:0;-1:-1:-1;;;;801:25:0;;;;;;;;;;-1:-1:-1;;;;834:15:0;;846:2;;;;834:15;:::i;:::-;;;;;;;;;;;;;;:22;;;;;-1:-1:-1;;834:22:0;;;;;;;;;-1:-1:-1;;;643:218:0:o;1200:147::-;1256:11;1270;1282:2;;1270:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1270:15:0;1293;;;;;;1337:5;1317:6;1324:2;;1317:10;;;;;;;:::i;1351:310::-;1407:11;1421;1433:2;;1421:15;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;-1:-1:-1;1421:15:0;1444;;;;;;1531:18;1552:6;1559:2;;1552:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;:17;;-1:-1:-1;;;1552:17:0;;;;;-1:-1:-1;1552:17:0;1577:22;;;;;;1615:6;1622:2;;1615:10;;;;;;;:::i;:::-;;;;;;;;;;;;;;1608:17;;-1:-1:-1;;;;;;1608:17:0;;;1615:10;1608:17;;;;;;;1615:10;1633:15;;1645:2;;;;1633:15;:::i;:::-;;;;;;;;;;;;;;:23;;;;;-1:-1:-1;;1633:23:0;;;;;;;;;-1:-1:-1;;;;1351:310: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;1372:479::-;1452:6;1460;1468;1521:2;1509:9;1500:7;1496:23;1492:32;1489:52;;;1537:1;1534;1527:12;1489:52;1577:9;1564:23;1610:18;1602:6;1599:30;1596:50;;;1642:1;1639;1632:12;1596:50;1681:59;1732:7;1723:6;1712:9;1708:22;1681:59;:::i;:::-;1759:8;;1655:85;;-1:-1:-1;1841:2:1;1826:18;;;;1813:32;;1372:479;-1:-1:-1;;;;1372:479:1:o;1856:273::-;2041:6;2033;2028:3;2015:33;1997:3;2067:16;;2092:13;;;2067:16;1856:273;-1:-1:-1;1856:273:1:o", + "sourcePath": "/home/andre_9a/cactus/packages/cactus-plugin-satp-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol", + "compiler": { + "name": "solc", + "version": "0.8.15+commit.e14f2714" + }, + "ast": { + "absolutePath": "/home/andre_9a/cactus/packages/cactus-plugin-satp-hermes/src/test/solidity/lock-asset-contract/lock-asset.sol", + "exportedSymbols": { + "Asset": [ + 8 + ], + "LockAsset": [ + 192 + ] + }, + "id": 193, + "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": 193, + "src": "438:68:0", + "visibility": "public" + }, + { + "abstract": false, + "baseContracts": [], + "canonicalName": "LockAsset", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 192, + "linearizedBaseContracts": [ + 192 + ], + "name": "LockAsset", + "nameLocation": "552:9:0", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 13, + "mutability": "mutable", + "name": "assets", + "nameLocation": "592:6:0", + "nodeType": "VariableDeclaration", + "scope": 192, + "src": "566: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": "575:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "nodeType": "Mapping", + "src": "566: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": "585:5:0" + }, + "referencedDeclaration": 8, + "src": "585:5:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", + "typeString": "struct Asset" + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 17, + "mutability": "mutable", + "name": "assetExists", + "nameLocation": "627:11:0", + "nodeType": "VariableDeclaration", + "scope": 192, + "src": "602:36:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string => bool)" + }, + "typeName": { + "id": 16, + "keyType": { + "id": 14, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "611:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "nodeType": "Mapping", + "src": "602:24:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string => bool)" + }, + "valueType": { + "id": 15, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "621:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + }, + "visibility": "internal" + }, + { + "body": { + "id": 58, + "nodeType": "Block", + "src": "702:159:0", + "statements": [ + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 27, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 25, + "name": "size", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 21, + "src": "718:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 26, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "723:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "718:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 24, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "710:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 28, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "710:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 29, + "nodeType": "ExpressionStatement", + "src": "710:15:0" + }, + { + "expression": { + "id": 35, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 30, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "733:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 32, + "indexExpression": { + "id": 31, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 19, + "src": "740:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "733:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 33, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "size", + "nodeType": "MemberAccess", + "referencedDeclaration": 7, + "src": "733:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 34, + "name": "size", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 21, + "src": "750:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "733:21:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 36, + "nodeType": "ExpressionStatement", + "src": "733:21:0" + }, + { + "expression": { + "id": 43, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 37, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "762:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 39, + "indexExpression": { + "id": 38, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 19, + "src": "769:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "762:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 40, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "creator", + "nodeType": "MemberAccess", + "referencedDeclaration": 3, + "src": "762:18:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 41, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4294967281, + "src": "783:3:0", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 42, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "src": "783:10:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "762:31:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 44, + "nodeType": "ExpressionStatement", + "src": "762:31:0" + }, + { + "expression": { + "id": 50, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 45, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "801:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 47, + "indexExpression": { + "id": 46, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 19, + "src": "808:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "801:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 48, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "801:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 49, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "821:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "801:25:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 51, + "nodeType": "ExpressionStatement", + "src": "801:25:0" + }, + { + "expression": { + "id": 56, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 52, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "834:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 54, + "indexExpression": { + "id": 53, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 19, + "src": "846:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "834:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "74727565", + "id": 55, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "852:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "834:22:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 57, + "nodeType": "ExpressionStatement", + "src": "834:22:0" + } + ] + }, + "functionSelector": "db9cc410", + "id": 59, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "createAsset", + "nameLocation": "652:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 22, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 19, + "mutability": "mutable", + "name": "id", + "nameLocation": "681:2:0", + "nodeType": "VariableDeclaration", + "scope": 59, + "src": "665:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 18, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "665:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 21, + "mutability": "mutable", + "name": "size", + "nameLocation": "690:4:0", + "nodeType": "VariableDeclaration", + "scope": 59, + "src": "685:9:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 20, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "685:4:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "663:32:0" + }, + "returnParameters": { + "id": 23, + "nodeType": "ParameterList", + "parameters": [], + "src": "702:0:0" + }, + "scope": 192, + "src": "643:218:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 71, + "nodeType": "Block", + "src": "938:30:0", + "statements": [ + { + "expression": { + "baseExpression": { + "id": 67, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "953:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 69, + "indexExpression": { + "id": 68, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61, + "src": "960:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "953:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "functionReturnParameters": 66, + "id": 70, + "nodeType": "Return", + "src": "946:17:0" + } + ] + }, + "functionSelector": "cd5286d0", + "id": 72, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getAsset", + "nameLocation": "874:8:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 62, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61, + "mutability": "mutable", + "name": "id", + "nameLocation": "899:2:0", + "nodeType": "VariableDeclaration", + "scope": 72, + "src": "883:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 60, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "883:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "882:20:0" + }, + "returnParameters": { + "id": 66, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 65, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 72, + "src": "924:12:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_memory_ptr", + "typeString": "struct Asset" + }, + "typeName": { + "id": 64, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 63, + "name": "Asset", + "nodeType": "IdentifierPath", + "referencedDeclaration": 8, + "src": "924:5:0" + }, + "referencedDeclaration": 8, + "src": "924:5:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage_ptr", + "typeString": "struct Asset" + } + }, + "visibility": "internal" + } + ], + "src": "923:14:0" + }, + "scope": 192, + "src": "865:103:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 94, + "nodeType": "Block", + "src": "1057:98:0", + "statements": [ + { + "assignments": [ + 78 + ], + "declarations": [ + { + "constant": false, + "id": 78, + "mutability": "mutable", + "name": "exists", + "nameLocation": "1070:6:0", + "nodeType": "VariableDeclaration", + "scope": 94, + "src": "1065:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 77, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1065:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 82, + "initialValue": { + "baseExpression": { + "id": 79, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1079:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 81, + "indexExpression": { + "id": 80, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 74, + "src": "1091:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1079:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1065:29:0" + }, + { + "expression": { + "arguments": [ + { + "id": 84, + "name": "exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1110:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 83, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1102:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 85, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1102:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 86, + "nodeType": "ExpressionStatement", + "src": "1102:15:0" + }, + { + "expression": { + "id": 92, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 87, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1126:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 89, + "indexExpression": { + "id": 88, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 74, + "src": "1133:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1126:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 90, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1126:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "74727565", + "id": 91, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1146:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "1126:24:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 93, + "nodeType": "ExpressionStatement", + "src": "1126:24:0" + } + ] + }, + "functionSelector": "5e82d0a6", + "id": 95, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "lockAsset", + "nameLocation": "1020:9:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 75, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 74, + "mutability": "mutable", + "name": "id", + "nameLocation": "1046:2:0", + "nodeType": "VariableDeclaration", + "scope": 95, + "src": "1030:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 73, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1030:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1029:20:0" + }, + "returnParameters": { + "id": 76, + "nodeType": "ParameterList", + "parameters": [], + "src": "1057:0:0" + }, + "scope": 192, + "src": "1011:144:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 117, + "nodeType": "Block", + "src": "1248:99:0", + "statements": [ + { + "assignments": [ + 101 + ], + "declarations": [ + { + "constant": false, + "id": 101, + "mutability": "mutable", + "name": "exists", + "nameLocation": "1261:6:0", + "nodeType": "VariableDeclaration", + "scope": 117, + "src": "1256:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 100, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1256:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 105, + "initialValue": { + "baseExpression": { + "id": 102, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1270:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 104, + "indexExpression": { + "id": 103, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 97, + "src": "1282:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1270:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1256:29:0" + }, + { + "expression": { + "arguments": [ + { + "id": 107, + "name": "exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 101, + "src": "1301:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 106, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1293:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 108, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1293:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 109, + "nodeType": "ExpressionStatement", + "src": "1293:15:0" + }, + { + "expression": { + "id": 115, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "baseExpression": { + "id": 110, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1317:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 112, + "indexExpression": { + "id": 111, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 97, + "src": "1324:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1317:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 113, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1317:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 114, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1337:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "1317:25:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 116, + "nodeType": "ExpressionStatement", + "src": "1317:25:0" + } + ] + }, + "functionSelector": "def60e0d", + "id": 118, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "unLockAsset", + "nameLocation": "1209:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 98, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 97, + "mutability": "mutable", + "name": "id", + "nameLocation": "1237:2:0", + "nodeType": "VariableDeclaration", + "scope": 118, + "src": "1221:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 96, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1221:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1220:20:0" + }, + "returnParameters": { + "id": 99, + "nodeType": "ParameterList", + "parameters": [], + "src": "1248:0:0" + }, + "scope": 192, + "src": "1200:147:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 155, + "nodeType": "Block", + "src": "1399:262:0", + "statements": [ + { + "assignments": [ + 124 + ], + "declarations": [ + { + "constant": false, + "id": 124, + "mutability": "mutable", + "name": "exists", + "nameLocation": "1412:6:0", + "nodeType": "VariableDeclaration", + "scope": 155, + "src": "1407:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 123, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1407:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 128, + "initialValue": { + "baseExpression": { + "id": 125, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1421:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 127, + "indexExpression": { + "id": 126, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1433:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1421:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1407:29:0" + }, + { + "expression": { + "arguments": [ + { + "id": 130, + "name": "exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 124, + "src": "1452:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 129, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1444:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 131, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1444:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 132, + "nodeType": "ExpressionStatement", + "src": "1444:15:0" + }, + { + "assignments": [ + 134 + ], + "declarations": [ + { + "constant": false, + "id": 134, + "mutability": "mutable", + "name": "assetIsLocked", + "nameLocation": "1536:13:0", + "nodeType": "VariableDeclaration", + "scope": 155, + "src": "1531:18:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 133, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1531:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 139, + "initialValue": { + "expression": { + "baseExpression": { + "id": 135, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1552:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 137, + "indexExpression": { + "id": 136, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1559:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1552:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 138, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1552:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1531:38:0" + }, + { + "expression": { + "arguments": [ + { + "id": 141, + "name": "assetIsLocked", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 134, + "src": "1585:13:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 140, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1577:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 142, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1577:22:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 143, + "nodeType": "ExpressionStatement", + "src": "1577:22:0" + }, + { + "expression": { + "id": 147, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "delete", + "prefix": true, + "src": "1608:17:0", + "subExpression": { + "baseExpression": { + "id": 144, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1615:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 146, + "indexExpression": { + "id": 145, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1622:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "1615:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 148, + "nodeType": "ExpressionStatement", + "src": "1608:17:0" + }, + { + "expression": { + "id": 153, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 149, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1633:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 151, + "indexExpression": { + "id": 150, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 120, + "src": "1645:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "1633:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "hexValue": "66616c7365", + "id": 152, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1651:5:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + "src": "1633:23:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 154, + "nodeType": "ExpressionStatement", + "src": "1633:23:0" + } + ] + }, + "functionSelector": "e24aa37c", + "id": 156, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deleteAsset", + "nameLocation": "1360:11:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 121, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 120, + "mutability": "mutable", + "name": "id", + "nameLocation": "1388:2:0", + "nodeType": "VariableDeclaration", + "scope": 156, + "src": "1372:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 119, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1372:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1371:20:0" + }, + "returnParameters": { + "id": 122, + "nodeType": "ParameterList", + "parameters": [], + "src": "1399:0:0" + }, + "scope": 192, + "src": "1351:310:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 167, + "nodeType": "Block", + "src": "1731:35:0", + "statements": [ + { + "expression": { + "baseExpression": { + "id": 163, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1746:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 165, + "indexExpression": { + "id": 164, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 158, + "src": "1758:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1746:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 162, + "id": 166, + "nodeType": "Return", + "src": "1739:22:0" + } + ] + }, + "functionSelector": "bc548275", + "id": 168, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isPresent", + "nameLocation": "1674:9:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 159, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 158, + "mutability": "mutable", + "name": "id", + "nameLocation": "1700:2:0", + "nodeType": "VariableDeclaration", + "scope": 168, + "src": "1684:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 157, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1684:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1683:20:0" + }, + "returnParameters": { + "id": 162, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 161, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 168, + "src": "1725:4:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 160, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1725:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "src": "1724:6:0" + }, + "scope": 192, + "src": "1665:101:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 190, + "nodeType": "Block", + "src": "1840:161:0", + "statements": [ + { + "assignments": [ + 176 + ], + "declarations": [ + { + "constant": false, + "id": 176, + "mutability": "mutable", + "name": "exists", + "nameLocation": "1853:6:0", + "nodeType": "VariableDeclaration", + "scope": 190, + "src": "1848:11:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 175, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1848:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "id": 180, + "initialValue": { + "baseExpression": { + "id": 177, + "name": "assetExists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 17, + "src": "1862:11:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_bool_$", + "typeString": "mapping(string memory => bool)" + } + }, + "id": 179, + "indexExpression": { + "id": 178, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 170, + "src": "1874:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1862:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1848:29:0" + }, + { + "expression": { + "arguments": [ + { + "id": 182, + "name": "exists", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 176, + "src": "1893:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 181, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 4294967278, + 4294967278 + ], + "referencedDeclaration": 4294967278, + "src": "1885:7:0", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 183, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1885:15:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 184, + "nodeType": "ExpressionStatement", + "src": "1885:15:0" + }, + { + "expression": { + "expression": { + "baseExpression": { + "id": 185, + "name": "assets", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "1979:6:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_string_memory_ptr_$_t_struct$_Asset_$8_storage_$", + "typeString": "mapping(string memory => struct Asset storage ref)" + } + }, + "id": 187, + "indexExpression": { + "id": 186, + "name": "id", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 170, + "src": "1986:2:0", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1979:10:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_Asset_$8_storage", + "typeString": "struct Asset storage ref" + } + }, + "id": 188, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "isLock", + "nodeType": "MemberAccess", + "referencedDeclaration": 5, + "src": "1979:17:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 174, + "id": 189, + "nodeType": "Return", + "src": "1972:24:0" + } + ] + }, + "functionSelector": "1ae4eb68", + "id": 191, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isAssetLocked", + "nameLocation": "1779:13:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 171, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 170, + "mutability": "mutable", + "name": "id", + "nameLocation": "1809:2:0", + "nodeType": "VariableDeclaration", + "scope": 191, + "src": "1793:18:0", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 169, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1793:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "src": "1792:20:0" + }, + "returnParameters": { + "id": 174, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 173, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 191, + "src": "1834:4:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 172, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1834:4:0", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + } + ], + "src": "1833:6:0" + }, + "scope": 192, + "src": "1770:231:0", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + } + ], + "scope": 193, + "src": "543:1460:0", + "usedErrors": [] + } + ], + "src": "413:1591:0" + }, + "functionHashes": { + "createAsset(string,uint256)": "db9cc410", + "deleteAsset(string)": "e24aa37c", + "getAsset(string)": "cd5286d0", + "isAssetLocked(string)": "1ae4eb68", + "isPresent(string)": "bc548275", + "lockAsset(string)": "5e82d0a6", + "unLockAsset(string)": "def60e0d" + }, + "gasEstimates": { + "creation": { + "codeDepositCost": "305800", + "executionCost": "343", + "totalCost": "306143" + }, + "external": { + "createAsset(string,uint256)": "infinite", + "deleteAsset(string)": "infinite", + "getAsset(string)": "infinite", + "isAssetLocked(string)": "infinite", + "isPresent(string)": "infinite", + "lockAsset(string)": "infinite", + "unLockAsset(string)": "infinite" + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/lock-asset.sol b/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/lock-asset.sol new file mode 100644 index 0000000000..adedf45613 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/solidity/lock-asset-contract/lock-asset.sol @@ -0,0 +1,70 @@ +// ***************************************************************************** +// IMPORTANT: If you update this code then make sure to recompile +// it and update the .json file as well so that they +// remain in sync for consistent test executions. +// With that said, there shouldn't be any reason to recompile this, like ever... +// ***************************************************************************** + +pragma solidity >=0.7.0; +struct Asset{ + address creator; + bool isLock; + uint size; +} + +contract LockAsset { + mapping (string => Asset) assets; + mapping (string => bool) assetExists; + + constructor() { + } + + function createAsset( string calldata id, uint size) public { + require(size>0); + assets[id].size= size; + assets[id].creator = msg.sender; + assets[id].isLock = false; + assetExists[id] = true; + } + + function getAsset(string calldata id) public view returns (Asset memory) { + return assets[id]; + } + + function lockAsset(string calldata id) public { + bool exists = assetExists[id]; + require(exists); + + assets[id].isLock = true; + } + + function unLockAsset(string calldata id) public { + bool exists = assetExists[id]; + require(exists); + + assets[id].isLock = false; + } + + function deleteAsset(string calldata id) public { + bool exists = assetExists[id]; + require(exists); + + //an asset can only be deleted if it is locked + bool assetIsLocked = assets[id].isLock; + require(assetIsLocked); + + delete assets[id]; + assetExists[id] = false; + } + + function isPresent(string calldata id) public view returns (bool) { + return assetExists[id]; + } + + function isAssetLocked(string calldata id) public view returns (bool) { + bool exists = assetExists[id]; + require(exists); + + return assets[id].isLock; + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/.gitignore b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/.gitignore new file mode 100644 index 0000000000..79bfe1a3e0 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/.gitignore @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules/ +jspm_packages/ +package-lock.json + +# Compiled TypeScript files +dist + diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/package.json b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/package.json new file mode 100644 index 0000000000..7e2b5a65e6 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/package.json @@ -0,0 +1,62 @@ +{ + "name": "asset-transfer-basic", + "version": "1.0.0", + "description": "Asset Transfer Basic contract implemented in TypeScript", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "scripts": { + "lint": "tslint -c tslint.json 'src/**/*.ts'", + "pretest": "npm run lint", + "test": "nyc mocha -r ts-node/register src/**/*.spec.ts", + "start": "fabric-chaincode-node start", + "build": "tsc", + "build:watch": "tsc -w", + "prepublishOnly": "npm run build" + }, + "engineStrict": true, + "author": "Hyperledger", + "license": "Apache-2.0", + "dependencies": { + "fabric-contract-api": "2.2.2", + "fabric-shim": "2.2.2" + }, + "devDependencies": { + "@types/chai": "4.3.0", + "@types/mocha": "5.2.7", + "@types/node": "18.11.9", + "@types/sinon": "5.0.7", + "@types/sinon-chai": "3.2.8", + "chai": "4.3.6", + "mocha": "5.2.0", + "nyc": "14.1.1", + "sinon": "7.5.0", + "sinon-chai": "3.7.0", + "ts-node": "7.0.1", + "tslint": "5.20.1", + "typescript": "5.3.3" + }, + "nyc": { + "extension": [ + ".ts", + ".tsx" + ], + "exclude": [ + "coverage/**", + "dist/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": true, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts new file mode 100644 index 0000000000..1e5f4dec06 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/asset.ts @@ -0,0 +1,23 @@ +/* + SPDX-License-Identifier: Apache-2.0 +*/ + +import { Object, Property } from "fabric-contract-api"; + +@Object() +export class Asset { + @Property() + public docType?: string; + + @Property() + public ID: string; + + @Property() + public isLocked: boolean; + + @Property() + public size: number; + + @Property() + public owner: string; +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts new file mode 100644 index 0000000000..8789a15b11 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/assetTransfer.ts @@ -0,0 +1,201 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + Context, + Contract, + Info, + Returns, + Transaction, +} from "fabric-contract-api"; +import { Asset } from "./asset"; + +@Info({ + title: "AssetTransfer", + description: "Smart contract for trading assets", +}) +export class AssetTransferContract extends Contract { + @Transaction() + public async InitLedger(ctx: Context): Promise { + const assets: Asset[] = [ + { + ID: "asset1", + size: 5, + isLocked: false, + owner: "owner1", + }, + { + ID: "asset2", + size: 5, + isLocked: false, + owner: "owner2", + }, + ]; + + for (const asset of assets) { + asset.docType = "asset"; + await ctx.stub.putState(asset.ID, Buffer.from(JSON.stringify(asset))); + console.info(`Asset ${asset.ID} initialized`); + } + } + + // CreateAsset issues a new asset to the world state with given details. + @Transaction() + public async CreateAsset( + ctx: Context, + id: string, + size: number, + owner: string, + ): Promise { + const asset: Asset = { + ID: id, + size: size, + isLocked: false, + owner: owner, + }; + await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + } + + // ReadAsset returns the asset stored in the world state with given id. + @Transaction(false) + public async ReadAsset(ctx: Context, id: string): Promise { + const assetJSON = await ctx.stub.getState(id); // get the asset from chaincode state + if (!assetJSON || assetJSON.length === 0) { + throw new Error(`The asset ${id} does not exist`); + } + return assetJSON.toString(); + } + + // UpdateAsset updates an existing asset in the world state with provided parameters. + @Transaction() + public async UpdateAsset( + ctx: Context, + id: string, + size: number, + owner: string, + ): Promise { + const exists = await this.AssetExists(ctx, id); + if (!exists) { + throw new Error(`The asset ${id} does not exist`); + } + + if (this.IsAssetLocked(ctx, id)) { + throw new Error(`The asset ${id} is locked`); + } + + // overwriting original asset with new asset + const assetString = await this.ReadAsset(ctx, id); + const asset: Asset = JSON.parse(assetString); + asset.size = size; + asset.owner = owner; + return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + } + + // TransferAsset changes the owner of an asset with given ID in the world state. + @Transaction() + public async TransferAsset( + ctx: Context, + id: string, + newOwner: string, + ): Promise { + const assetString = await this.ReadAsset(ctx, id); + const asset: Asset = JSON.parse(assetString); + asset.owner = newOwner; + return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + } + + // DeleteAsset deletes a given asset from the world state. + @Transaction() + public async DeleteAsset(ctx: Context, id: string): Promise { + const exists = await this.AssetExists(ctx, id); + if (!exists) { + throw new Error(`The asset ${id} does not exist`); + } + + return ctx.stub.deleteState(id); + } + + // AssetExists returns true when asset with given ID exists in world state. + @Transaction(false) + @Returns("boolean") + public async AssetExists(ctx: Context, id: string): Promise { + const assetJSON = await ctx.stub.getState(id); + return assetJSON && assetJSON.length > 0; + } + + // IsAssetLocked returns true when asset with given ID is locked in world state. + @Transaction(false) + @Returns("boolean") + public async IsAssetLocked(ctx: Context, id: string): Promise { + const assetJSON = await ctx.stub.getState(id); + + if (assetJSON && assetJSON.length > 0) { + const asset = JSON.parse(assetJSON.toString()); + return asset.isLocked; + } else { + throw new Error(`The asset ${id} does not exist`); + } + } + + @Transaction(false) + @Returns("boolean") + public async LockAsset(ctx: Context, id: string): Promise { + const exists = await this.AssetExists(ctx, id); + + if (!exists) { + throw new Error(`The asset ${id} does not exist`); + } + + // if (this.IsAssetLocked(ctx, id)) { + // throw new Error(`The asset ${id} is already locked`); + // } + + const assetString = await this.ReadAsset(ctx, id); + const asset: Asset = JSON.parse(assetString); + asset.isLocked = true; + await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + return true; + } + + @Transaction(false) + @Returns("boolean") + public async UnlockAsset(ctx: Context, id: string): Promise { + const exists = await this.AssetExists(ctx, id); + + if (!exists) { + throw new Error(`The asset ${id} does not exist`); + } + + const assetString = await this.ReadAsset(ctx, id); + const asset: Asset = JSON.parse(assetString); + asset.isLocked = false; + await ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); + return true; + } + + // GetAllAssets returns all assets found in the world state. + @Transaction(false) + @Returns("string") + public async GetAllAssets(ctx: Context): Promise { + const allResults = []; + // range query with empty string for startKey and endKey does an open-ended query of all assets in the chaincode namespace. + const iterator = await ctx.stub.getStateByRange("", ""); + let result = await iterator.next(); + while (!result.done) { + const strValue = Buffer.from(result.value.value.toString()).toString( + "utf8", + ); + let record; + try { + record = JSON.parse(strValue); + } catch (err) { + console.log(err); + record = strValue; + } + allResults.push({ Key: result.value.key, Record: record }); + result = await iterator.next(); + } + return JSON.stringify(allResults); + } +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/index.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/index.ts new file mode 100644 index 0000000000..020a09f410 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/src/index.ts @@ -0,0 +1,9 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AssetTransferContract } from "./assetTransfer"; + +export { AssetTransferContract } from "./assetTransfer"; + +export const contracts: any[] = [AssetTransferContract]; diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/tsconfig.json b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/tsconfig.json new file mode 100644 index 0000000000..80d8e12d8c --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist", + "target": "es2017", + "moduleResolution": "node", + "module": "commonjs", + "declaration": true, + "sourceMap": true + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "./src/**/*.spec.ts" + ] +} diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/besu-ethereum-asset-transfer.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/besu-ethereum-asset-transfer.test.ts new file mode 100644 index 0000000000..d73e9cfe39 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/besu-ethereum-asset-transfer.test.ts @@ -0,0 +1,778 @@ +import "jest-extended"; +import { + IListenOptions, + LogLevelDesc, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; +import { Server as SocketIoServer } from "socket.io"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + Constants, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import bodyParser from "body-parser"; +import http from "http"; +import { + Web3SigningCredentialType as Web3SigningCredentialTypeBesu, + PluginLedgerConnectorBesu, + EthContractInvocationType as EthContractInvocationTypeBesu, + ReceiptType, + IPluginLedgerConnectorBesuOptions, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, +} from "@hyperledger/cactus-test-geth-ledger"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { + BesuTestLedger, + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import Web3 from "web3"; +import { Account } from "web3-core"; +import express from "express"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const logLevel: LogLevelDesc = "INFO"; + +let besuLedger: BesuTestLedger; +let besuContractName: string; +let rpcApiHttpHostBesu: string; +let rpcApiWsHost: string; +let web3Besu: Web3; +let firstHighNetWorthAccount: string; +let besuConnector: PluginLedgerConnectorBesu; +let besuKeyPair: { privateKey: string }; +let testEthAccountBesu: Account; +let keychainPluginBesu: PluginKeychainMemory; + +const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; +const containerImageVersion = "2023-07-27-2a8c48ed6"; +const keychainEntryKey = uuidv4(); +let testEthAccount: Account; +let web3Eth: InstanceType; +let addressInfo; +let address: string; +let port: number; +let apiHost: string; +let apiConfig; +let ethereumLedger: GethTestLedger; +let ethereumApiClient: EthereumApi; +let ethereumConnector: PluginLedgerConnectorEthereum; +let rpcApiHttpHostEthereum: string; +let keychainPluginEthereum: PluginKeychainMemory; +const expressAppEthereum = express(); +expressAppEthereum.use(bodyParser.json({ limit: "250mb" })); +const ethereumServer = http.createServer(expressAppEthereum); +const wsApi = new SocketIoServer(ethereumServer, { + path: Constants.SocketIoConnectionPathV1, +}); + +let hephaestus: CcModelHephaestus; +let hephaestusOptions: IPluginCcModelHephaestusOptions; +let modeledTransactions: number; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "besu-ethereum-asset-transfer.test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + besuLedger = new BesuTestLedger(); + await besuLedger.start(); + + rpcApiHttpHostBesu = await besuLedger.getRpcApiHttpHost(); + rpcApiWsHost = await besuLedger.getRpcApiWsHost(); + web3Besu = new Web3(rpcApiHttpHostBesu); + firstHighNetWorthAccount = besuLedger.getGenesisAccountPubKey(); + + testEthAccountBesu = await besuLedger.createEthTestAccount(); + + besuKeyPair = { + privateKey: besuLedger.getGenesisAccountPrivKey(), + }; + + besuContractName = "LockAsset"; + + const keychainEntryValue = besuKeyPair.privateKey; + const keychainEntryKey = uuidv4(); + keychainPluginBesu = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPluginBesu.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPluginBesu], + }); + + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost: rpcApiHttpHostBesu, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + besuConnector = new PluginLedgerConnectorBesu(options); + pluginRegistry.add(besuConnector); + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccountBesu.address, + value: 10e9, + gas: 1000000, + }, + }); + const balance = await web3Besu.eth.getBalance(testEthAccountBesu.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toBeGreaterThan(10e9); + + log.info("Connector initialized"); + + const deployOut = await besuConnector.deployContract({ + keychainId: keychainPluginBesu.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + log.info("Contract Deployed successfully"); + } + { + ethereumLedger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ethereumLedger.start(); + + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server: ethereumServer, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + + apiConfig = new Configuration({ basePath: apiHost }); + ethereumApiClient = new EthereumApi(apiConfig); + rpcApiHttpHostEthereum = await ethereumLedger.getRpcApiHttpHost(); + web3Eth = new Web3(rpcApiHttpHostEthereum); + testEthAccount = web3Eth.eth.accounts.create(); + + log.info("Create PluginKeychainMemory..."); + const keychainEntryValue = testEthAccount.privateKey; + keychainPluginEthereum = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + + keychainPluginEthereum.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + ethereumConnector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHostEthereum, + logLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPluginEthereum] }), + }); + + await ethereumConnector.getOrCreateWebServices(); + await ethereumConnector.registerWebServices(expressAppEthereum, wsApi); + + log.info("Deploy Contract..."); + const deployOut = await ethereumApiClient.deployContract({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.data).toBeTruthy(); + expect(deployOut.data.transactionReceipt).toBeTruthy(); + expect(deployOut.data.transactionReceipt.contractAddress).toBeTruthy(); + log.info("contract deployed successfully"); + + const initTransferValue = web3Eth.utils.toWei("5000", "ether"); + await ethereumApiClient.runTransactionV1({ + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: WHALE_ACCOUNT_ADDRESS, + to: testEthAccount.address, + value: initTransferValue, + }, + }); + const balance = await web3Eth.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(balance.toString()).toBe(initTransferValue); + } + { + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: logLevel, + besuTxObservable: besuConnector.getTxSubjectObservable(), + ethTxObservable: ethereumConnector.getTxSubjectObservable(), + sourceLedger: LedgerType.Besu2X, + targetLedger: LedgerType.Ethereum, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + } + { + const { success: createResBesu1 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["asset1_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu1).toBeTruthy(); + + const { success: createResBesu2 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["asset2_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu2).toBeTruthy(); + + const { success: createResBesu3 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["test1_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu3).toBeTruthy(); + + const { success: createResBesu4 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["test2_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu4).toBeTruthy(); + + const { success: createResBesu5 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["test3_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu5).toBeTruthy(); + + const { success: createResBesu6 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["test4_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu6).toBeTruthy(); + + const createResEth1 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["test5_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth1).toBeTruthy(); + } + { + hephaestus.monitorTransactions(0); + + hephaestus.setCaseId("cctx1"); + + const { success: lockResBesu1 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["asset1_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu1).toBeTruthy(); + + const { success: deleteResBesu1 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "deleteAsset", + params: ["asset1_besu"], + signingCredential: { + ethAccount: testEthAccountBesu.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deleteResBesu1).toBeTruthy(); + + const createResEth1 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth1).toBeTruthy(); + modeledTransactions = 3; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + hephaestus.setCaseId("cctx2"); + + const { success: lockResBesu2 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["asset2_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu2).toBeTruthy(); + + const { success: deleteResBesu2 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "deleteAsset", + params: ["asset2_besu"], + signingCredential: { + ethAccount: testEthAccountBesu.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deleteResBesu2).toBeTruthy(); + + const createResEth2 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset2_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth2).toBeTruthy(); + + modeledTransactions = 6; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const model = await hephaestus.createModel(); + expect(model).toBeTruthy(); + expect(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)) + .toBeTruthy; + hephaestus.setIsModeling(false); + } +}); + +test("Tx1 - Unlock after lock", async () => { + hephaestus.setCaseId("unmodeled_cctx1"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: lockResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["test1_asset_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const { success: unlockResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "unLockAsset", + params: ["test1_asset_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(unlockResBesu).toBeTruthy(); + + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx2 - Skip escrow", async () => { + hephaestus.setCaseId("unmodeled_cctx2"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const createResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["test2_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx3 - Skip burn", async () => { + hephaestus.setCaseId("unmodeled_cctx3"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: lockResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["test3_asset_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const createResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["test3_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx4 - Double mint", async () => { + hephaestus.setCaseId("unmodeled_cctx4"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: lockResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["test4_asset_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const { success: deleteResBesu1 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "deleteAsset", + params: ["test4_asset_besu"], + signingCredential: { + ethAccount: testEthAccountBesu.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deleteResBesu1).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(2); + + const createResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["test4_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + modeledTransactions += 3; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const createResEth2 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["test4_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth2).toBeTruthy(); + + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); +}); + +test("Tx5 - Asset transfer from Ethereum to Besu", async () => { + hephaestus.setCaseId("unmodeled_cctx5"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["test5_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); + + const deleteResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["test5_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + + const { success: createResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["test5_asset_eth", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + + expect(hephaestus.numberEventsNonConformedLog).toEqual(3); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +afterAll(async () => { + console.log(hephaestus.ccModel.getCrossChainState()); + console.log(hephaestus.ccModel); + + await besuLedger.stop(); + await besuLedger.destroy(); + + await ethereumConnector.shutdown(); + await ethereumLedger.stop(); + await ethereumLedger.destroy(); + await Servers.shutdown(ethereumServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-conformance-checking.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-conformance-checking.test.ts new file mode 100644 index 0000000000..3aeaf99bd0 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-conformance-checking.test.ts @@ -0,0 +1,350 @@ +/* + * Copyright 2020-2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel: LogLevelDesc = "info"; + +import "jest-extended"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import Web3 from "web3"; +import { v4 as uuidv4 } from "uuid"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + Constants, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { + IListenOptions, + Logger, + LoggerProvider, + Servers, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, +} from "@hyperledger/cactus-test-geth-ledger"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Account } from "web3-core"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const log: Logger = LoggerProvider.getOrCreate({ + label: "cross-chain-model-conformance-checking.test", + level: testLogLevel, +}); +log.info("Test started"); + +const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; +const containerImageVersion = "2023-07-27-2a8c48ed6"; + +describe("Test cross-chain model serialization and conformance checking", () => { + const keychainEntryKey = uuidv4(); + let testEthAccount: Account, + web3: InstanceType, + addressInfo, + address: string, + port: number, + apiHost: string, + apiConfig, + ledger: GethTestLedger, + apiClient: EthereumApi, + connector: PluginLedgerConnectorEthereum, + rpcApiHttpHost: string, + keychainPlugin: PluginKeychainMemory; + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + let hephaestus: CcModelHephaestus; + let hephaestusOptions: IPluginCcModelHephaestusOptions; + + let totalTxs = 0; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel: testLogLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel: testLogLevel }); + fail("Pruning didn't throw OK"); + }); + + ledger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ledger.start(); + + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + + apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new EthereumApi(apiConfig); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(); + + log.info("Create PluginKeychainMemory..."); + const keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel: testLogLevel, + }); + + keychainPlugin.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHost, + logLevel: testLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + log.info("Deploy Contract..."); + const deployOut = await apiClient.deployContract({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.data).toBeTruthy(); + expect(deployOut.data.transactionReceipt).toBeTruthy(); + expect(deployOut.data.transactionReceipt.contractAddress).toBeTruthy(); + log.info("contract deployed successfully"); + + const initTransferValue = web3.utils.toWei("5000", "ether"); + await apiClient.runTransactionV1({ + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: WHALE_ACCOUNT_ADDRESS, + to: testEthAccount.address, + value: initTransferValue, + }, + }); + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(balance.toString()).toBe(initTransferValue); + + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: testLogLevel, + ethTxObservable: connector.getTxSubjectObservable(), + sourceLedger: LedgerType.Ethereum, + targetLedger: LedgerType.Ethereum, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + }); + + test("Monitor Ethereum transactions and create cross-chain model", async () => { + hephaestus.setCaseId("ETHEREUM_MONITORING"); + hephaestus.monitorTransactions(); + + const numberOfCases = 3; + const txsPerCase = 3; + let caseNumber = 1; + + while (numberOfCases >= caseNumber) { + const createResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1", 5], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(createResEth.data).toBeTruthy(); + + const lockResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(lockResEth.data).toBeTruthy(); + expect(lockResEth.status).toBe(200); + + const deleteResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(deleteResEth.data).toBeTruthy(); + expect(deleteResEth.status).toBe(200); + + caseNumber++; + } + + totalTxs = txsPerCase * numberOfCases; + expect(hephaestus.numberEventsLog).toEqual(totalTxs); + + const model = await hephaestus.createModel(); + expect(model).toBeTruthy(); + expect(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)) + .toBeTruthy; + + hephaestus.setIsModeling(false); + }); + + test("Check conformance of unmodeled transaction when they happen", async () => { + const createResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset2", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(createResEth.data).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const lockResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset2"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(lockResEth.data).toBeTruthy(); + expect(lockResEth.status).toBe(200); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(2); + + const deleteResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset2"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(deleteResEth.data).toBeTruthy(); + expect(deleteResEth.status).toBe(200); + + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(totalTxs + 3); + }); + + afterAll(async () => { + log.info("Shutdown connector..."); + await connector.shutdown(); + + log.info("Stop and destroy the test ledger..."); + await ledger.stop(); + await ledger.destroy(); + + log.info("Shutdown server..."); + await Servers.shutdown(server); + + log.info("Prune docker..."); + const pruning = pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-serialization.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-serialization.test.ts new file mode 100644 index 0000000000..d08ec9062d --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/cross-chain-model-serialization.test.ts @@ -0,0 +1,288 @@ +/* + * Copyright 2020-2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel: LogLevelDesc = "info"; + +import "jest-extended"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import Web3 from "web3"; +import { v4 as uuidv4 } from "uuid"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + Constants, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { + IListenOptions, + Logger, + LoggerProvider, + Servers, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, +} from "@hyperledger/cactus-test-geth-ledger"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Account } from "web3-core"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const log: Logger = LoggerProvider.getOrCreate({ + label: "cross-chain-model-serialization.test", + level: testLogLevel, +}); +log.info("Test started"); + +const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; +const containerImageVersion = "2023-07-27-2a8c48ed6"; + +describe("Test cross-chain model serialization", () => { + const keychainEntryKey = uuidv4(); + let testEthAccount: Account, + web3: InstanceType, + addressInfo, + address: string, + port: number, + apiHost: string, + apiConfig, + ledger: GethTestLedger, + apiClient: EthereumApi, + connector: PluginLedgerConnectorEthereum, + rpcApiHttpHost: string, + keychainPlugin: PluginKeychainMemory; + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + let hephaestus: CcModelHephaestus; + let hephaestusOptions: IPluginCcModelHephaestusOptions; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel: testLogLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel: testLogLevel }); + fail("Pruning didn't throw OK"); + }); + + ledger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ledger.start(); + + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + + apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new EthereumApi(apiConfig); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(); + + log.info("Create PluginKeychainMemory..."); + const keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel: testLogLevel, + }); + + keychainPlugin.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHost, + logLevel: testLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + log.info("Deploy Contract..."); + const deployOut = await apiClient.deployContract({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.data).toBeTruthy(); + expect(deployOut.data.transactionReceipt).toBeTruthy(); + expect(deployOut.data.transactionReceipt.contractAddress).toBeTruthy(); + log.info("contract deployed successfully"); + + const initTransferValue = web3.utils.toWei("5000", "ether"); + await apiClient.runTransactionV1({ + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: WHALE_ACCOUNT_ADDRESS, + to: testEthAccount.address, + value: initTransferValue, + }, + }); + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(balance.toString()).toBe(initTransferValue); + + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: testLogLevel, + ethTxObservable: connector.getTxSubjectObservable(), + sourceLedger: LedgerType.Ethereum, + targetLedger: LedgerType.Ethereum, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + }); + + test("Monitor Ethereum transactions and create cross-chain model", async () => { + hephaestus.setCaseId("ETHEREUM_MONITORING"); + hephaestus.monitorTransactions(); + + const numberOfCases = 3; + const txsPerCase = 3; + let caseNumber = 1; + + while (numberOfCases >= caseNumber) { + const createResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1", 5], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(createResEth.data).toBeTruthy(); + + const lockResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(lockResEth.data).toBeTruthy(); + expect(lockResEth.status).toBe(200); + + const deleteResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(deleteResEth.data).toBeTruthy(); + expect(deleteResEth.status).toBe(200); + + caseNumber++; + } + + const totalTxs = txsPerCase * numberOfCases; + + expect(hephaestus.numberEventsLog).toEqual(totalTxs); + + const model = await hephaestus.createModel(); + + expect(model).toBeTruthy(); + expect(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)) + .toBeTruthy; + console.log(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)); + }); + + afterAll(async () => { + log.info("Shutdown connector..."); + await connector.shutdown(); + + log.info("Stop and destroy the test ledger..."); + await ledger.stop(); + await ledger.destroy(); + + log.info("Shutdown server..."); + await Servers.shutdown(server); + + log.info("Prune docker..."); + const pruning = pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/ethereum-fabric-asset-transfer.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/ethereum-fabric-asset-transfer.test.ts new file mode 100644 index 0000000000..2080879ade --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/ethereum-fabric-asset-transfer.test.ts @@ -0,0 +1,868 @@ +import "jest-extended"; +import { + IListenOptions, + LogLevelDesc, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; +import { Server as SocketIoServer } from "socket.io"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + Constants, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { DiscoveryOptions } from "fabric-network"; +import bodyParser from "body-parser"; +import path from "path"; +import http, { Server } from "http"; +import fs from "fs-extra"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, +} from "@hyperledger/cactus-test-geth-ledger"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { + Configuration as ConfigurationFabric, + DefaultEventHandlerStrategy, + FabricSigningCredential, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FileBase64, + ChainCodeProgrammingLanguage, + FabricContractInvocationType, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import { + Containers, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import Web3 from "web3"; +import { Account } from "web3-core"; +import express from "express"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const logLevel: LogLevelDesc = "INFO"; + +let fabricServer: Server; +let fabricSigningCredential: FabricSigningCredential; +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let channelName: string; +let config: ConfigurationFabric; +let fabricApiClient: FabricApi; +let fabricConnector: PluginLedgerConnectorFabric; + +const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; +const containerImageVersion = "2023-07-27-2a8c48ed6"; +const keychainEntryKey = uuidv4(); +let testEthAccount: Account; +let web3Eth: InstanceType; +let addressInfo; +let address: string; +let port: number; +let apiHost: string; +let apiConfig; +let ethereumLedger: GethTestLedger; +let ethereumApiClient: EthereumApi; +let ethereumConnector: PluginLedgerConnectorEthereum; +let rpcApiHttpHostEthereum: string; +let keychainPluginEthereum: PluginKeychainMemory; +const expressAppEthereum = express(); +expressAppEthereum.use(bodyParser.json({ limit: "250mb" })); +const ethereumServer = http.createServer(expressAppEthereum); +const wsApi = new SocketIoServer(ethereumServer, { + path: Constants.SocketIoConnectionPathV1, +}); + +let hephaestus: CcModelHephaestus; +let hephaestusOptions: IPluginCcModelHephaestusOptions; +let modeledTransactions: number; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "ethereum-fabric-asset-transfer.test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: FABRIC_25_LTS_AIO_IMAGE_VERSION, + envVars: new Map([["FABRIC_VERSION", FABRIC_25_LTS_AIO_FABRIC_VERSION]]), + logLevel, + }); + + await fabricLedger.start(); + log.info("Fabric Ledger started"); + + const channelId = "mychannel"; + channelName = channelId; + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + log.info("admin enrolled"); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user1"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPluginFabric = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPluginFabric], + }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressAppFabric = express(); + expressAppFabric.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressAppFabric); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + const apiUrl = `http://${address}:${port}`; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressAppFabric); + + config = new ConfigurationFabric({ basePath: apiUrl }); + + fabricApiClient = new FabricApi(config); + + // deploy contracts ... + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ ├── index.ts + // │ └── ITraceableContract.ts + // ├── tsconfig.json + // -------- + const sourceFiles: FileBase64[] = []; + { + 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, + }); + } + + const res = await fabricApiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + ], + caFile: + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.ORDERER_TLS_ROOTCERT_FILE, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = res.data; + expect(res.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + expect(packageIds).toBeTruthy(); + expect(packageIds).toBeArray(); + + expect(approveForMyOrgList).toBeTruthy(); + expect(approveForMyOrgList).toBeArray(); + + expect(installList).toBeTruthy(); + expect(installList).toBeArray(); + expect(queryInstalledList).toBeTruthy(); + expect(queryInstalledList).toBeArray(); + + expect(commit).toBeTruthy(); + expect(packaging).toBeTruthy(); + expect(queryCommitted).toBeTruthy(); + log.info("Contract deployed"); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + } + { + ethereumLedger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ethereumLedger.start(); + + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server: ethereumServer, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + + apiConfig = new Configuration({ basePath: apiHost }); + ethereumApiClient = new EthereumApi(apiConfig); + rpcApiHttpHostEthereum = await ethereumLedger.getRpcApiHttpHost(); + web3Eth = new Web3(rpcApiHttpHostEthereum); + testEthAccount = web3Eth.eth.accounts.create(); + + log.info("Create PluginKeychainMemory..."); + const keychainEntryValue = testEthAccount.privateKey; + keychainPluginEthereum = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + + keychainPluginEthereum.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + ethereumConnector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHostEthereum, + logLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPluginEthereum] }), + }); + + await ethereumConnector.getOrCreateWebServices(); + await ethereumConnector.registerWebServices(expressAppEthereum, wsApi); + + log.info("Deploy Contract..."); + const deployOut = await ethereumApiClient.deployContract({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.data).toBeTruthy(); + expect(deployOut.data.transactionReceipt).toBeTruthy(); + expect(deployOut.data.transactionReceipt.contractAddress).toBeTruthy(); + log.info("contract deployed successfully"); + + const initTransferValue = web3Eth.utils.toWei("5000", "ether"); + await ethereumApiClient.runTransactionV1({ + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: WHALE_ACCOUNT_ADDRESS, + to: testEthAccount.address, + value: initTransferValue, + }, + }); + const balance = await web3Eth.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(balance.toString()).toBe(initTransferValue); + } + { + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: logLevel, + ethTxObservable: ethereumConnector.getTxSubjectObservable(), + fabricTxObservable: fabricConnector.getTxSubjectObservable(), + sourceLedger: LedgerType.Ethereum, + targetLedger: LedgerType.Fabric2, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + } + { + const createResEth1 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth1).toBeTruthy(); + + const createResEth2 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset2_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth2).toBeTruthy(); + + const createResEth3 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["tx1_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth3).toBeTruthy(); + + const createResEth4 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["tx2_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth4).toBeTruthy(); + + const createResEth5 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["tx3_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth5).toBeTruthy(); + + const createResEth6 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["tx4_asset_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth6).toBeTruthy(); + + const createResFabric = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx5_asset_fabric", "10", "owner1"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric).toBeTruthy(); + } + { + hephaestus.monitorTransactions(0); + + hephaestus.setCaseId("cctx1"); + + const lockResEth1 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset1_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth1).toBeTruthy(); + + const deleteResEth1 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset1_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth1).toBeTruthy(); + + const createResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset1_fabric", "10", "owner_fabric"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric1).toBeTruthy(); + modeledTransactions = 3; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + hephaestus.setCaseId("cctx2"); + + const lockResEth2 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset2_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth2).toBeTruthy(); + + const deleteResEth2 = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset2_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth2).toBeTruthy(); + + const createResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset2_fabric", "10", "owner_fabric"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric2).toBeTruthy(); + + modeledTransactions = 6; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const model = await hephaestus.createModel(); + expect(model).toBeTruthy(); + expect(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)) + .toBeTruthy; + hephaestus.setIsModeling(false); + } +}); + +test("Tx1 - Unlock after lock", async () => { + hephaestus.setCaseId("unmodeled_cctx1"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["tx1_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const unlockResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "unLockAsset", + params: ["tx1_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(unlockResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx2 - Skip escrow", async () => { + hephaestus.setCaseId("unmodeled_cctx2"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const createResFabric = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx2_asset_fabric", "10", "owner1"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx3 - Skip burn", async () => { + hephaestus.setCaseId("unmodeled_cctx3"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["tx3_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const createResFabric = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx3_asset_fabric", "10", "owner2"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx4 - Double mint", async () => { + hephaestus.setCaseId("unmodeled_cctx4"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["tx4_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const deleteResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["tx4_asset_eth"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(2); + + const createResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx4_asset_fabric", "10", "owner3"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric1).toBeTruthy(); + modeledTransactions += 3; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + + const createResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx4_asset_fabric", "10", "owner3"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric2).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx5 - Asset transfer from Fabric to Ethereum", async () => { + hephaestus.setCaseId("unmodeled_cctx5"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx5_asset_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric1).toBeTruthy(); + + const deleteResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx5_asset_fabric"], + methodName: "DeleteAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(deleteResFabric1).toBeTruthy(); + + const createResEth = await ethereumApiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPluginEthereum.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset2_eth", 10], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + + expect(hephaestus.numberEventsNonConformedLog).toEqual(3); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +afterAll(async () => { + await fabricLedger.stop(); + await fabricLedger.destroy(); + await Servers.shutdown(fabricServer); + + await ethereumConnector.shutdown(); + await ethereumLedger.stop(); + await ethereumLedger.destroy(); + await Servers.shutdown(ethereumServer); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/fabric-besu-asset-transfer.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/fabric-besu-asset-transfer.test.ts new file mode 100644 index 0000000000..4766fee7f5 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/fabric-besu-asset-transfer.test.ts @@ -0,0 +1,800 @@ +import "jest-extended"; +import { + IListenOptions, + LogLevelDesc, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { LedgerType } from "@hyperledger/cactus-core-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { DiscoveryOptions } from "fabric-network"; +import bodyParser from "body-parser"; +import path from "path"; +import http, { Server } from "http"; +import fs from "fs-extra"; +import { + Web3SigningCredentialType as Web3SigningCredentialTypeBesu, + PluginLedgerConnectorBesu, + EthContractInvocationType as EthContractInvocationTypeBesu, + ReceiptType, + IPluginLedgerConnectorBesuOptions, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import { + Configuration as ConfigurationFabric, + DefaultEventHandlerStrategy, + FabricSigningCredential, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FileBase64, + ChainCodeProgrammingLanguage, + FabricContractInvocationType, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import { + BesuTestLedger, + Containers, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import Web3 from "web3"; +import { Account } from "web3-core"; +import express from "express"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { CrossChainModelType } from "../../../main/typescript/models/crosschain-model"; + +const logLevel: LogLevelDesc = "INFO"; + +let fabricServer: Server; +let fabricSigningCredential: FabricSigningCredential; +let fabricLedger: FabricTestLedgerV1; +let fabricContractName: string; +let channelName: string; +let config: ConfigurationFabric; +let fabricApiClient: FabricApi; +let fabricConnector: PluginLedgerConnectorFabric; + +let besuLedger: BesuTestLedger; +let besuContractName: string; +let rpcApiHttpHostBesu: string; +let rpcApiWsHost: string; +let web3Besu: Web3; +let firstHighNetWorthAccount: string; +let besuConnector: PluginLedgerConnectorBesu; +let besuKeyPair: { privateKey: string }; +let testEthAccountBesu: Account; +let keychainPluginBesu: PluginKeychainMemory; + +let hephaestus: CcModelHephaestus; +let hephaestusOptions: IPluginCcModelHephaestusOptions; +let modeledTransactions: number; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "fabric-besu-asset-transfer.test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: FABRIC_25_LTS_AIO_IMAGE_VERSION, + envVars: new Map([["FABRIC_VERSION", FABRIC_25_LTS_AIO_FABRIC_VERSION]]), + logLevel, + }); + + await fabricLedger.start(); + log.info("Fabric Ledger started"); + + const channelId = "mychannel"; + channelName = channelId; + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + log.info("admin enrolled"); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user1"; + const keychainEntryValue = JSON.stringify(userIdentity); + + const keychainPluginFabric = new PluginKeychainMemory({ + instanceId: keychainInstanceId, + keychainId, + logLevel, + backend: new Map([ + [keychainEntryKey, keychainEntryValue], + ["some-other-entry-key", "some-other-entry-value"], + ]), + }); + + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPluginFabric], + }); + + const discoveryOptions: DiscoveryOptions = { + enabled: true, + asLocalhost: true, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressAppFabric = express(); + expressAppFabric.use(bodyParser.json({ limit: "250mb" })); + fabricServer = http.createServer(expressAppFabric); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server: fabricServer, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + const apiUrl = `http://${address}:${port}`; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressAppFabric); + + config = new ConfigurationFabric({ basePath: apiUrl }); + + fabricApiClient = new FabricApi(config); + + // deploy contracts ... + fabricContractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ ├── index.ts + // │ └── ITraceableContract.ts + // ├── tsconfig.json + // -------- + const sourceFiles: FileBase64[] = []; + { + 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, + }); + } + + const res = await fabricApiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: fabricContractName, + targetOrganizations: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + ], + caFile: + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.ORDERER_TLS_ROOTCERT_FILE, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = res.data; + expect(res.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + expect(packageIds).toBeTruthy(); + expect(packageIds).toBeArray(); + + expect(approveForMyOrgList).toBeTruthy(); + expect(approveForMyOrgList).toBeArray(); + + expect(installList).toBeTruthy(); + expect(installList).toBeArray(); + expect(queryInstalledList).toBeTruthy(); + expect(queryInstalledList).toBeArray(); + + expect(commit).toBeTruthy(); + expect(packaging).toBeTruthy(); + expect(queryCommitted).toBeTruthy(); + log.info("Contract deployed"); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + } + { + besuLedger = new BesuTestLedger(); + await besuLedger.start(); + + rpcApiHttpHostBesu = await besuLedger.getRpcApiHttpHost(); + rpcApiWsHost = await besuLedger.getRpcApiWsHost(); + web3Besu = new Web3(rpcApiHttpHostBesu); + firstHighNetWorthAccount = besuLedger.getGenesisAccountPubKey(); + + testEthAccountBesu = await besuLedger.createEthTestAccount(); + + besuKeyPair = { + privateKey: besuLedger.getGenesisAccountPrivKey(), + }; + + besuContractName = "LockAsset"; + + const keychainEntryValue = besuKeyPair.privateKey; + const keychainEntryKey = uuidv4(); + keychainPluginBesu = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPluginBesu.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPluginBesu], + }); + + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost: rpcApiHttpHostBesu, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + besuConnector = new PluginLedgerConnectorBesu(options); + pluginRegistry.add(besuConnector); + + await besuConnector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + consistencyStrategy: { + blockConfirmations: 0, + receiptType: ReceiptType.NodeTxPoolAck, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccountBesu.address, + value: 10e9, + gas: 1000000, + }, + }); + const balance = await web3Besu.eth.getBalance(testEthAccountBesu.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toBeGreaterThan(10e9); + + log.info("Connector initialized"); + + const deployOut = await besuConnector.deployContract({ + keychainId: keychainPluginBesu.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + log.info("Contract Deployed successfully"); + } + { + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: logLevel, + besuTxObservable: besuConnector.getTxSubjectObservable(), + fabricTxObservable: fabricConnector.getTxSubjectObservable(), + sourceLedger: LedgerType.Fabric2, + targetLedger: LedgerType.Besu2X, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + } + { + const createResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset1_fabric", "10", "owner1"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric1).toBeTruthy(); + + const createResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset2_fabric", "10", "owner2"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric2).toBeTruthy(); + + const createResFabric3 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx1_asset_fabric", "10", "owner3"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric3).toBeTruthy(); + + const createResFabric4 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx2_asset_fabric", "10", "owner4"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric4).toBeTruthy(); + + const createResFabric5 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx3_asset_fabric", "10", "owner5"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric5).toBeTruthy(); + + const createResFabric6 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx4_asset_fabric", "10", "owner6"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric6).toBeTruthy(); + + const { success: createResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["tx5_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + } + { + hephaestus.monitorTransactions(0); + + hephaestus.setCaseId("cctx1"); + + const lockResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset1_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric1).toBeTruthy(); + + const deleteResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset1_fabric"], + methodName: "DeleteAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(deleteResFabric1).toBeTruthy(); + + const { success: createResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["asset1_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + modeledTransactions = 3; + + hephaestus.setCaseId("cctx2"); + + const lockResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset2_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric2).toBeTruthy(); + + const deleteResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["asset2_fabric"], + methodName: "DeleteAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(deleteResFabric2).toBeTruthy(); + + const { success: createResBesu2 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["asset2_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu2).toBeTruthy(); + + modeledTransactions = 6; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const model = await hephaestus.createModel(); + expect(model).toBeTruthy(); + expect(hephaestus.ccModel.getModel(CrossChainModelType.PetriNet)) + .toBeTruthy; + hephaestus.setIsModeling(false); + } +}); + +test("Tx1 - Unlock after lock", async () => { + hephaestus.setCaseId("unmodeled_cctx1"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx1_asset_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric1).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const unlockResFabric1 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx1_asset_fabric"], + methodName: "UnlockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(unlockResFabric1).toBeTruthy(); + + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx2 - Skip escrow", async () => { + hephaestus.setCaseId("unmodeled_cctx2"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: createResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["tx2_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx3 - Skip burn", async () => { + hephaestus.setCaseId("unmodeled_cctx3"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResFabric = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx3_asset_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const { success: createResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["tx3_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(2); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +test("Tx4 - Double mint", async () => { + hephaestus.setCaseId("unmodeled_cctx4"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const lockResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx4_asset_fabric"], + methodName: "LockAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(lockResFabric2).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(1); + + const deleteResFabric2 = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx4_asset_fabric"], + methodName: "DeleteAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(deleteResFabric2).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(2); + + const { success: createResBesu1 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["tx4_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu1).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + modeledTransactions += 3; + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: createResBesu2 } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "createAsset", + params: ["tx4_asset_besu", 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu2).toBeTruthy(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(1); +}); + +test("Tx5 - Asset transfer from Besu to Fabric", async () => { + hephaestus.setCaseId("unmodeled_cctx5"); + hephaestus.purgeNonConformedEvents(); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsNonConformedLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); + + const { success: lockResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "lockAsset", + params: ["tx5_asset_besu"], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu).toBeTruthy(); + + const { success: deleteResBesu } = await besuConnector.invokeContract({ + contractName: besuContractName, + keychainId: keychainPluginBesu.getKeychainId(), + invocationType: EthContractInvocationTypeBesu.Send, + methodName: "deleteAsset", + params: ["tx5_asset_besu"], + signingCredential: { + ethAccount: testEthAccountBesu.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialTypeBesu.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deleteResBesu).toBeTruthy(); + + const createResFabric = await fabricApiClient.runTransactionV1({ + contractName: fabricContractName, + channelName, + params: ["tx5_asset_fabric", "10", "fabric_owner"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric).toBeTruthy(); + + expect(hephaestus.numberEventsNonConformedLog).toEqual(3); + expect(hephaestus.numberEventsUnmodeledLog).toEqual(0); + expect(hephaestus.numberEventsLog).toEqual(modeledTransactions); +}); + +afterAll(async () => { + await fabricLedger.stop(); + await fabricLedger.destroy(); + await Servers.shutdown(fabricServer); + + await besuLedger.stop(); + await besuLedger.destroy(); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-besu-events.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-besu-events.test.ts new file mode 100644 index 0000000000..23573fc85c --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-besu-events.test.ts @@ -0,0 +1,242 @@ +import "jest-extended"; +import { LogLevelDesc, LoggerProvider } from "@hyperledger/cactus-common"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { v4 as uuidv4 } from "uuid"; +import { + BesuTestLedger, + pruneDockerAllIfGithubAction, + Containers, +} from "@hyperledger/cactus-test-tooling"; +import { + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + EthContractInvocationType, + ReceiptType, + IPluginLedgerConnectorBesuOptions, +} from "@hyperledger/cactus-plugin-ledger-connector-besu"; +import Web3 from "web3"; +import { Account } from "web3-core"; + +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import { LedgerType } from "@hyperledger/cactus-core-api"; + +const logLevel: LogLevelDesc = "INFO"; + +let besuLedger: BesuTestLedger; +let contractName: string; + +let rpcApiHttpHost: string; +let rpcApiWsHost: string; +let web3: Web3; +let firstHighNetWorthAccount: string; +let connector: PluginLedgerConnectorBesu; +let besuKeyPair: { privateKey: string }; +let testEthAccount: Account; +let keychainPlugin: PluginKeychainMemory; +const BESU_ASSET_ID = uuidv4(); + +let hephaestus: CcModelHephaestus; +let hephaestusOptions: IPluginCcModelHephaestusOptions; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "monitor-4-besu-events.test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + besuLedger = new BesuTestLedger(); + await besuLedger.start(); + + rpcApiHttpHost = await besuLedger.getRpcApiHttpHost(); + rpcApiWsHost = await besuLedger.getRpcApiWsHost(); + web3 = new Web3(rpcApiHttpHost); + firstHighNetWorthAccount = besuLedger.getGenesisAccountPubKey(); + + testEthAccount = await besuLedger.createEthTestAccount(); + + besuKeyPair = { + privateKey: besuLedger.getGenesisAccountPrivKey(), + }; + + contractName = "LockAsset"; + + const keychainEntryValue = besuKeyPair.privateKey; + const keychainEntryKey = uuidv4(); + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + keychainPlugin.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + const pluginRegistry = new PluginRegistry({ + plugins: [keychainPlugin], + }); + + const options: IPluginLedgerConnectorBesuOptions = { + instanceId: uuidv4(), + rpcApiHttpHost, + rpcApiWsHost, + pluginRegistry, + logLevel, + }; + connector = new PluginLedgerConnectorBesu(options); + pluginRegistry.add(connector); + + 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 balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(parseInt(balance, 10)).toBeGreaterThan(10e9); + + log.info("Connector initialized"); + + const deployOut = await connector.deployContract({ + keychainId: keychainPlugin.getKeychainId(), + contractName: LockAssetContractJson.contractName, + contractAbi: LockAssetContractJson.abi, + constructorArgs: [], + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + bytecode: LockAssetContractJson.bytecode, + gas: 1000000, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.transactionReceipt).toBeTruthy(); + expect(deployOut.transactionReceipt.contractAddress).toBeTruthy(); + log.info("Contract Deployed successfully"); + } + { + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: logLevel, + besuTxObservable: connector.getTxSubjectObservable(), + sourceLedger: LedgerType.Besu2X, + targetLedger: LedgerType.Besu2X, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + } +}); + +test("monitor Besu transactions", async () => { + hephaestus.setCaseId("BESU_MONITORING"); + hephaestus.monitorTransactions(); + + const { success: createResBesu } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: [BESU_ASSET_ID, 10], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(createResBesu).toBeTruthy(); + + const { success: lockResBesu } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: testEthAccount.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(lockResBesu).toBeTruthy(); + + const { success: isPresentResBesu } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "isPresent", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: testEthAccount.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(isPresentResBesu).toBeTruthy(); + + const { success: deleteResBesu } = await connector.invokeContract({ + contractName, + keychainId: keychainPlugin.getKeychainId(), + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: [BESU_ASSET_ID], + signingCredential: { + ethAccount: testEthAccount.address, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PrivateKeyHex, + }, + gas: 1000000, + }); + expect(deleteResBesu).toBeTruthy(); + + expect(hephaestus.numberEventsLog).toEqual(4); + + await hephaestus.persistCrossChainLogCsv("example-dummy-besu-4-events"); + await hephaestus.persistCrossChainLogJson("example-dummy-besu-4-events"); +}); + +afterAll(async () => { + await besuLedger.stop(); + await besuLedger.destroy(); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-ethereum-events.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-ethereum-events.test.ts new file mode 100644 index 0000000000..386eba9c3d --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-ethereum-events.test.ts @@ -0,0 +1,293 @@ +/* + * Copyright 2020-2022 Hyperledger Cactus Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +const testLogLevel: LogLevelDesc = "info"; + +import "jest-extended"; +import express from "express"; +import bodyParser from "body-parser"; +import http from "http"; +import Web3 from "web3"; +import { v4 as uuidv4 } from "uuid"; +import { Server as SocketIoServer } from "socket.io"; +import { AddressInfo } from "net"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + Configuration, + Constants, + LedgerType, +} from "@hyperledger/cactus-core-api"; +import { + IListenOptions, + Logger, + LoggerProvider, + Servers, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { + Containers, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import { + GethTestLedger, + WHALE_ACCOUNT_ADDRESS, +} from "@hyperledger/cactus-test-geth-ledger"; +import { + EthContractInvocationType, + PluginLedgerConnectorEthereum, + Web3SigningCredentialType, + DefaultApi as EthereumApi, +} from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { Account } from "web3-core"; +import LockAssetContractJson from "../../solidity/lock-asset-contract/LockAsset.json"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; + +const log: Logger = LoggerProvider.getOrCreate({ + label: "monitor-4-ethereum-events.test", + level: testLogLevel, +}); +log.info("Test started"); + +const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; +const containerImageVersion = "2023-07-27-2a8c48ed6"; + +describe("Ethereum contract deploy and invoke while monitoring", () => { + const keychainEntryKey = uuidv4(); + let testEthAccount: Account, + web3: InstanceType, + addressInfo, + address: string, + port: number, + apiHost: string, + apiConfig, + ledger: GethTestLedger, + apiClient: EthereumApi, + connector: PluginLedgerConnectorEthereum, + rpcApiHttpHost: string, + keychainPlugin: PluginKeychainMemory; + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + const wsApi = new SocketIoServer(server, { + path: Constants.SocketIoConnectionPathV1, + }); + + let hephaestus: CcModelHephaestus; + let hephaestusOptions: IPluginCcModelHephaestusOptions; + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel: testLogLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel: testLogLevel }); + fail("Pruning didn't throw OK"); + }); + + ledger = new GethTestLedger({ + containerImageName, + containerImageVersion, + }); + await ledger.start(); + + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server, + }; + addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + ({ address, port } = addressInfo); + apiHost = `http://${address}:${port}`; + + apiConfig = new Configuration({ basePath: apiHost }); + apiClient = new EthereumApi(apiConfig); + rpcApiHttpHost = await ledger.getRpcApiHttpHost(); + web3 = new Web3(rpcApiHttpHost); + testEthAccount = web3.eth.accounts.create(); + + log.info("Create PluginKeychainMemory..."); + const keychainEntryValue = testEthAccount.privateKey; + keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel: testLogLevel, + }); + + keychainPlugin.set( + LockAssetContractJson.contractName, + JSON.stringify(LockAssetContractJson), + ); + + log.info("Create PluginLedgerConnectorEthereum..."); + connector = new PluginLedgerConnectorEthereum({ + rpcApiHttpHost: rpcApiHttpHost, + logLevel: testLogLevel, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + }); + + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp, wsApi); + + log.info("Deploy Contract..."); + const deployOut = await apiClient.deployContract({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deployOut).toBeTruthy(); + expect(deployOut.data).toBeTruthy(); + expect(deployOut.data.transactionReceipt).toBeTruthy(); + expect(deployOut.data.transactionReceipt.contractAddress).toBeTruthy(); + log.info("contract deployed successfully"); + + const initTransferValue = web3.utils.toWei("5000", "ether"); + await apiClient.runTransactionV1({ + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + transactionConfig: { + from: WHALE_ACCOUNT_ADDRESS, + to: testEthAccount.address, + value: initTransferValue, + }, + }); + const balance = await web3.eth.getBalance(testEthAccount.address); + expect(balance).toBeTruthy(); + expect(balance.toString()).toBe(initTransferValue); + + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: testLogLevel, + ethTxObservable: connector.getTxSubjectObservable(), + sourceLedger: LedgerType.Ethereum, + targetLedger: LedgerType.Ethereum, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + }); + + test("monitor Ethereum transactions", async () => { + hephaestus.setCaseId("ETHEREUM_MONITORING"); + hephaestus.monitorTransactions(); + + const createResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "createAsset", + params: ["asset1", 5], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(createResEth).toBeTruthy(); + expect(createResEth.data).toBeTruthy(); + + const lockResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "lockAsset", + params: ["asset1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(lockResEth).toBeTruthy(); + expect(lockResEth.data).toBeTruthy(); + expect(lockResEth.status).toBe(200); + + const isPresentResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "isPresent", + params: ["asset1", "owner1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(isPresentResEth).toBeTruthy(); + expect(isPresentResEth.data).toBeTruthy(); + expect(isPresentResEth.status).toBe(200); + + const deleteResEth = await apiClient.invokeContractV1({ + contract: { + contractName: LockAssetContractJson.contractName, + keychainId: keychainPlugin.getKeychainId(), + }, + invocationType: EthContractInvocationType.Send, + methodName: "deleteAsset", + params: ["asset1", "owner1"], + web3SigningCredential: { + ethAccount: WHALE_ACCOUNT_ADDRESS, + secret: "", + type: Web3SigningCredentialType.GethKeychainPassword, + }, + }); + expect(deleteResEth).toBeTruthy(); + expect(deleteResEth.data).toBeTruthy(); + expect(deleteResEth.status).toBe(200); + + expect(hephaestus.numberEventsLog).toEqual(4); + + await hephaestus.persistCrossChainLogCsv("example-dummy-ethereum-4-events"); + await hephaestus.persistCrossChainLogJson( + "example-dummy-ethereum-4-events", + ); + }); + + afterAll(async () => { + log.info("Shutdown connector..."); + await connector.shutdown(); + + log.info("Stop and destroy the test ledger..."); + await ledger.stop(); + await ledger.destroy(); + + log.info("Shutdown server..."); + await Servers.shutdown(server); + + log.info("Prune docker..."); + const pruning = pruneDockerAllIfGithubAction({ logLevel: testLogLevel }); + await expect(pruning).resolves.toBeTruthy(); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-fabric-events.test.ts b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-fabric-events.test.ts new file mode 100644 index 0000000000..a0bbe4c065 --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/src/test/typescript/integration/monitor-4-fabric-events.test.ts @@ -0,0 +1,362 @@ +import "jest-extended"; +import { + IListenOptions, + LogLevelDesc, + LoggerProvider, + Servers, +} from "@hyperledger/cactus-common"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { DiscoveryOptions } from "fabric-network"; +import bodyParser from "body-parser"; +import path from "path"; +import http, { Server } from "http"; +import fs from "fs-extra"; +import { + Configuration, + DefaultEventHandlerStrategy, + FabricSigningCredential, + IPluginLedgerConnectorFabricOptions, + PluginLedgerConnectorFabric, + DefaultApi as FabricApi, + FileBase64, + ChainCodeProgrammingLanguage, + FabricContractInvocationType, +} from "@hyperledger/cactus-plugin-ledger-connector-fabric"; +import { + Containers, + FABRIC_25_LTS_AIO_FABRIC_VERSION, + FABRIC_25_LTS_AIO_IMAGE_VERSION, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + FabricTestLedgerV1, + pruneDockerAllIfGithubAction, +} from "@hyperledger/cactus-test-tooling"; +import express from "express"; +import { AddressInfo } from "net"; +import { v4 as uuidv4 } from "uuid"; +import { CcModelHephaestus } from "../../../main/typescript/plugin-ccmodel-hephaestus"; +import { IPluginCcModelHephaestusOptions } from "../../../main/typescript"; +import { LedgerType } from "@hyperledger/cactus-core-api"; + +let server: Server; + +let fabricSigningCredential: FabricSigningCredential; +const logLevel: LogLevelDesc = "INFO"; + +let fabricLedger: FabricTestLedgerV1; +let contractName: string; +let channelName: string; + +let config: Configuration; +let apiClient: FabricApi; + +let fabricConnector: PluginLedgerConnectorFabric; +const FABRIC_ASSET_ID = uuidv4(); + +let hephaestus: CcModelHephaestus; +let hephaestusOptions: IPluginCcModelHephaestusOptions; + +const log = LoggerProvider.getOrCreate({ + level: logLevel, + label: "monitor-4-fabric-events.test", +}); + +beforeAll(async () => { + pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); + + { + fabricLedger = new FabricTestLedgerV1({ + emitContainerLogs: true, + publishAllPorts: true, + imageName: "ghcr.io/hyperledger/cactus-fabric2-all-in-one", + imageVersion: FABRIC_25_LTS_AIO_IMAGE_VERSION, + envVars: new Map([["FABRIC_VERSION", FABRIC_25_LTS_AIO_FABRIC_VERSION]]), + logLevel, + }); + + await fabricLedger.start(); + log.info("Fabric Ledger started"); + + const channelId = "mychannel"; + channelName = channelId; + + const connectionProfile = await fabricLedger.getConnectionProfileOrg1(); + expect(connectionProfile).not.toBeUndefined(); + const enrollAdminOut = await fabricLedger.enrollAdmin(); + const adminWallet = enrollAdminOut[1]; + const [userIdentity] = await fabricLedger.enrollUser(adminWallet); + const sshConfig = await fabricLedger.getSshConfig(); + log.info("admin enrolled"); + + const keychainInstanceId = uuidv4(); + const keychainId = uuidv4(); + const keychainEntryKey = "user1"; + 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, + }; + + const pluginOptions: IPluginLedgerConnectorFabricOptions = { + instanceId: uuidv4(), + dockerBinary: "/usr/local/bin/docker", + peerBinary: "/fabric-samples/bin/peer", + goBinary: "/usr/local/go/bin/go", + pluginRegistry, + cliContainerEnv: FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + sshConfig, + logLevel, + connectionProfile, + discoveryOptions, + eventHandlerOptions: { + strategy: DefaultEventHandlerStrategy.NetworkScopeAllfortx, + commitTimeout: 300, + }, + }; + fabricConnector = new PluginLedgerConnectorFabric(pluginOptions); + + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + server = http.createServer(expressApp); + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + const apiUrl = `http://${address}:${port}`; + + await fabricConnector.getOrCreateWebServices(); + await fabricConnector.registerWebServices(expressApp); + + config = new Configuration({ basePath: apiUrl }); + + apiClient = new FabricApi(config); + + // deploy contracts ... + contractName = "basic-asset-transfer-2"; + const contractRelPath = + "../fabric-contracts/lock-asset/chaincode-typescript"; + + const contractDir = path.join(__dirname, contractRelPath); + + // ├── package.json + // ├── src + // │ ├── assetTransfer.ts + // │ ├── asset.ts + // │ ├── index.ts + // │ └── ITraceableContract.ts + // ├── tsconfig.json + // -------- + const sourceFiles: FileBase64[] = []; + { + 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, + }); + } + + const res = await apiClient.deployContractV1({ + channelId, + ccVersion: "1.0.0", + sourceFiles, + ccName: contractName, + targetOrganizations: [ + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1, + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_2, + ], + caFile: + FABRIC_25_LTS_FABRIC_SAMPLES_ENV_INFO_ORG_1.ORDERER_TLS_ROOTCERT_FILE, + ccLabel: "basic-asset-transfer-2", + ccLang: ChainCodeProgrammingLanguage.Typescript, + ccSequence: 1, + orderer: "orderer.example.com:7050", + ordererTLSHostnameOverride: "orderer.example.com", + connTimeout: 60, + }); + + const { packageIds, lifecycle, success } = res.data; + expect(res.status).toBe(200); + expect(success).toBe(true); + expect(lifecycle).not.toBeUndefined(); + + const { + approveForMyOrgList, + installList, + queryInstalledList, + commit, + packaging, + queryCommitted, + } = lifecycle; + + expect(packageIds).toBeTruthy(); + expect(packageIds).toBeArray(); + + expect(approveForMyOrgList).toBeTruthy(); + expect(approveForMyOrgList).toBeArray(); + + expect(installList).toBeTruthy(); + expect(installList).toBeArray(); + expect(queryInstalledList).toBeTruthy(); + expect(queryInstalledList).toBeArray(); + + expect(commit).toBeTruthy(); + expect(packaging).toBeTruthy(); + expect(queryCommitted).toBeTruthy(); + log.info("Contract deployed"); + + fabricSigningCredential = { + keychainId, + keychainRef: keychainEntryKey, + }; + } + { + hephaestusOptions = { + instanceId: uuidv4(), + logLevel: logLevel, + fabricTxObservable: fabricConnector.getTxSubjectObservable(), + sourceLedger: LedgerType.Fabric2, + targetLedger: LedgerType.Fabric2, + }; + + hephaestus = new CcModelHephaestus(hephaestusOptions); + expect(hephaestus).toBeTruthy(); + log.info("hephaestus plugin initialized successfully"); + } +}); + +test("monitor Fabric transactions", async () => { + hephaestus.setCaseId("FABRIC_MONITORING"); + hephaestus.monitorTransactions(); + + const createResFabric = await apiClient.runTransactionV1({ + contractName, + channelName, + params: [FABRIC_ASSET_ID, "10", "owner1"], + methodName: "CreateAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(createResFabric).toBeTruthy(); + + const transferResFabric = await apiClient.runTransactionV1({ + contractName, + channelName, + params: [FABRIC_ASSET_ID, "owner2"], + methodName: "TransferAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(transferResFabric).toBeTruthy(); + + const transferBackResFabric = await apiClient.runTransactionV1({ + contractName, + channelName, + params: [FABRIC_ASSET_ID, "owner1"], + methodName: "TransferAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(transferBackResFabric).toBeTruthy(); + + const deleteResFabric = await apiClient.runTransactionV1({ + contractName, + channelName, + params: [FABRIC_ASSET_ID], + methodName: "DeleteAsset", + invocationType: FabricContractInvocationType.Send, + signingCredential: fabricSigningCredential, + }); + expect(deleteResFabric).toBeTruthy(); + + expect(hephaestus.numberEventsLog).toEqual(4); + + await hephaestus.persistCrossChainLogCsv("example-dummy-fabric-4-events"); + await hephaestus.persistCrossChainLogJson("example-dummy-fabric-4-events"); +}); + +afterAll(async () => { + await fabricLedger.stop(); + await fabricLedger.destroy(); + await Servers.shutdown(server); + + await pruneDockerAllIfGithubAction({ logLevel }) + .then(() => { + log.info("Pruning throw OK"); + }) + .catch(async () => { + await Containers.logDiagnostics({ logLevel }); + fail("Pruning didn't throw OK"); + }); +}); diff --git a/packages/cactus-plugin-ccmodel-hephaestus/tsconfig.json b/packages/cactus-plugin-ccmodel-hephaestus/tsconfig.json new file mode 100644 index 0000000000..8ab9da859c --- /dev/null +++ b/packages/cactus-plugin-ccmodel-hephaestus/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib/", + "declarationDir": "dist/types", + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ccmodel-hephaestus.tsbuildinfo" + }, + "include": [ + "./src", + "./src/test/solidity/lock-asset-contract/*.json", + ], + "exclude":[ + "./src/test/typescript/fabric-contracts/lock-asset/chaincode-typescript/**/*.ts" + ], + "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/yarn.lock b/yarn.lock index 1bcf0ecd68..15fa81f552 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10068,6 +10068,34 @@ __metadata: languageName: unknown linkType: soft +"@hyperledger/cactus-plugin-ccmodel-hephaestus@workspace:packages/cactus-plugin-ccmodel-hephaestus": + version: 0.0.0-use.local + resolution: "@hyperledger/cactus-plugin-ccmodel-hephaestus@workspace:packages/cactus-plugin-ccmodel-hephaestus" + dependencies: + "@hyperledger/cactus-common": "npm:2.0.0" + "@hyperledger/cactus-core": "npm:2.0.0" + "@hyperledger/cactus-core-api": "npm:2.0.0" + "@hyperledger/cactus-plugin-keychain-memory": "npm:2.0.0" + "@hyperledger/cactus-plugin-ledger-connector-besu": "npm:2.0.0" + "@hyperledger/cactus-plugin-ledger-connector-ethereum": "npm:2.0.0" + "@hyperledger/cactus-plugin-ledger-connector-fabric": "npm:2.0.0" + "@hyperledger/cactus-test-geth-ledger": "npm:2.0.0" + "@hyperledger/cactus-test-tooling": "npm:2.0.0" + "@types/express": "npm:5.0.0" + "@types/uuid": "npm:10.0.0" + body-parser: "npm:1.20.3" + express: "npm:4.21.0" + fabric-network: "npm:2.2.20" + run-time-error-cjs: "npm:1.4.0" + rxjs: "npm:7.8.1" + socket.io: "npm:4.6.2" + typescript-optional: "npm:2.0.1" + uuid: "npm:10.0.0" + web3: "npm:1.6.1" + web3-core: "npm:1.6.1" + languageName: unknown + linkType: soft + "@hyperledger/cactus-plugin-consortium-manual@npm:2.0.0, @hyperledger/cactus-plugin-consortium-manual@workspace:packages/cactus-plugin-consortium-manual": version: 0.0.0-use.local resolution: "@hyperledger/cactus-plugin-consortium-manual@workspace:packages/cactus-plugin-consortium-manual"